runwork 0.19.0 → 0.20.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 +1273 -882
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -853,6 +853,28 @@ var init_client = __esm(() => {
|
|
|
853
853
|
init_http();
|
|
854
854
|
});
|
|
855
855
|
|
|
856
|
+
// src/utils/atomic-json.ts
|
|
857
|
+
import { existsSync, mkdirSync, readFileSync as readFileSync2, renameSync, writeFileSync } from "fs";
|
|
858
|
+
import { dirname } from "path";
|
|
859
|
+
function writeJsonAtomic(path, value, opts = {}) {
|
|
860
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
861
|
+
const json = JSON.stringify(value, null, 2);
|
|
862
|
+
const tmpPath = `${path}.${process.pid}.tmp`;
|
|
863
|
+
writeFileSync(tmpPath, json, opts.mode !== undefined ? { mode: opts.mode } : {});
|
|
864
|
+
renameSync(tmpPath, path);
|
|
865
|
+
}
|
|
866
|
+
function readJsonOrNull(path) {
|
|
867
|
+
if (!existsSync(path))
|
|
868
|
+
return null;
|
|
869
|
+
try {
|
|
870
|
+
const parsed = JSON.parse(readFileSync2(path, "utf-8"));
|
|
871
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
872
|
+
} catch {
|
|
873
|
+
return null;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
var init_atomic_json = () => {};
|
|
877
|
+
|
|
856
878
|
// src/auth/store.ts
|
|
857
879
|
var exports_store = {};
|
|
858
880
|
__export(exports_store, {
|
|
@@ -861,7 +883,7 @@ __export(exports_store, {
|
|
|
861
883
|
getCredentials: () => getCredentials,
|
|
862
884
|
clearCredentials: () => clearCredentials
|
|
863
885
|
});
|
|
864
|
-
import { readFileSync as
|
|
886
|
+
import { readFileSync as readFileSync3, existsSync as existsSync2, unlinkSync } from "fs";
|
|
865
887
|
import { join as join2 } from "path";
|
|
866
888
|
import { homedir } from "os";
|
|
867
889
|
function getCredentials() {
|
|
@@ -880,21 +902,20 @@ function getCredentials() {
|
|
|
880
902
|
defaultWorkspaceName: undefined
|
|
881
903
|
};
|
|
882
904
|
}
|
|
883
|
-
if (!
|
|
905
|
+
if (!existsSync2(CREDENTIALS_FILE))
|
|
884
906
|
return null;
|
|
885
907
|
try {
|
|
886
|
-
const raw =
|
|
908
|
+
const raw = readFileSync3(CREDENTIALS_FILE, "utf-8");
|
|
887
909
|
return JSON.parse(raw);
|
|
888
910
|
} catch {
|
|
889
911
|
return null;
|
|
890
912
|
}
|
|
891
913
|
}
|
|
892
914
|
function saveCredentials(credentials) {
|
|
893
|
-
|
|
894
|
-
writeFileSync(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2), { mode: 384 });
|
|
915
|
+
writeJsonAtomic(CREDENTIALS_FILE, credentials, { mode: 384 });
|
|
895
916
|
}
|
|
896
917
|
function clearCredentials() {
|
|
897
|
-
if (
|
|
918
|
+
if (existsSync2(CREDENTIALS_FILE)) {
|
|
898
919
|
unlinkSync(CREDENTIALS_FILE);
|
|
899
920
|
}
|
|
900
921
|
}
|
|
@@ -917,6 +938,7 @@ function requireAuth() {
|
|
|
917
938
|
}
|
|
918
939
|
var RUNWORK_DIR, CREDENTIALS_FILE;
|
|
919
940
|
var init_store = __esm(() => {
|
|
941
|
+
init_atomic_json();
|
|
920
942
|
RUNWORK_DIR = join2(homedir(), ".runwork");
|
|
921
943
|
CREDENTIALS_FILE = join2(RUNWORK_DIR, ".credentials");
|
|
922
944
|
});
|
|
@@ -934,7 +956,7 @@ var execFileSync = (file, args, options) => {
|
|
|
934
956
|
var init_subprocess = () => {};
|
|
935
957
|
|
|
936
958
|
// src/git/credentials.ts
|
|
937
|
-
import { existsSync as
|
|
959
|
+
import { existsSync as existsSync3 } from "fs";
|
|
938
960
|
function buildHelperValue(execPath, scriptPath) {
|
|
939
961
|
const normalised = execPath.replace(/\\/g, "/");
|
|
940
962
|
const runtimeName = normalised.split("/").pop()?.toLowerCase() ?? "";
|
|
@@ -1015,7 +1037,7 @@ function helperBinaryStatus(value) {
|
|
|
1015
1037
|
const helperPath = pathMatch?.[1] ?? pathMatch?.[2] ?? "";
|
|
1016
1038
|
if (!helperPath)
|
|
1017
1039
|
return { ok: true, path: null };
|
|
1018
|
-
return { ok:
|
|
1040
|
+
return { ok: existsSync3(helperPath), path: helperPath };
|
|
1019
1041
|
}
|
|
1020
1042
|
async function ensureGitCredentialHelper(baseUrl) {
|
|
1021
1043
|
let origin;
|
|
@@ -1087,10 +1109,21 @@ var init_credentials = __esm(() => {
|
|
|
1087
1109
|
});
|
|
1088
1110
|
|
|
1089
1111
|
// src/auth/login-flow.ts
|
|
1090
|
-
|
|
1112
|
+
function decorateLoginUrl(loginUrl, options) {
|
|
1113
|
+
const params = [];
|
|
1114
|
+
if (options?.register)
|
|
1115
|
+
params.push("mode=register");
|
|
1116
|
+
if (options?.provider)
|
|
1117
|
+
params.push(`provider=${encodeURIComponent(options.provider)}`);
|
|
1118
|
+
if (params.length === 0)
|
|
1119
|
+
return loginUrl;
|
|
1120
|
+
return `${loginUrl}${loginUrl.includes("?") ? "&" : "?"}${params.join("&")}`;
|
|
1121
|
+
}
|
|
1122
|
+
async function performLogin(baseUrl, options) {
|
|
1091
1123
|
const url = baseUrl || DEFAULT_BASE_URL2;
|
|
1092
1124
|
const client = new ApiClient({ apiKey: "", email: "", baseUrl: url });
|
|
1093
|
-
const { sessionId, loginUrl } = await client.initiateLogin();
|
|
1125
|
+
const { sessionId, loginUrl: rawLoginUrl } = await client.initiateLogin();
|
|
1126
|
+
const loginUrl = decorateLoginUrl(rawLoginUrl, options);
|
|
1094
1127
|
const open = await import("open");
|
|
1095
1128
|
await open.default(loginUrl);
|
|
1096
1129
|
console.log(`If browser didn't open, visit: ${loginUrl}`);
|
|
@@ -1165,7 +1198,7 @@ function extractBaseUrl(loginUrl) {
|
|
|
1165
1198
|
}
|
|
1166
1199
|
}
|
|
1167
1200
|
async function pollAndSave(client, sessionId, baseUrl) {
|
|
1168
|
-
const maxAttempts =
|
|
1201
|
+
const maxAttempts = 360;
|
|
1169
1202
|
const pollInterval = 5000;
|
|
1170
1203
|
for (let i = 0;i < maxAttempts; i++) {
|
|
1171
1204
|
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
@@ -1280,7 +1313,7 @@ var init_identity = __esm(() => {
|
|
|
1280
1313
|
});
|
|
1281
1314
|
|
|
1282
1315
|
// src/git/preflight.ts
|
|
1283
|
-
import { existsSync as
|
|
1316
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1284
1317
|
import { win32 as winPath } from "path";
|
|
1285
1318
|
import { homedir as homedir2 } from "os";
|
|
1286
1319
|
function tryRun(bin) {
|
|
@@ -1302,9 +1335,9 @@ function whereGit() {
|
|
|
1302
1335
|
const out = buf.toString("utf-8");
|
|
1303
1336
|
const lines = out.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
1304
1337
|
const exe = lines.find((line) => /\.exe$/i.test(line));
|
|
1305
|
-
if (exe &&
|
|
1338
|
+
if (exe && existsSync4(exe))
|
|
1306
1339
|
return exe;
|
|
1307
|
-
const fallback = lines.find((line) =>
|
|
1340
|
+
const fallback = lines.find((line) => existsSync4(line));
|
|
1308
1341
|
return fallback ?? null;
|
|
1309
1342
|
} catch {
|
|
1310
1343
|
return null;
|
|
@@ -1329,7 +1362,7 @@ function registryGit() {
|
|
|
1329
1362
|
continue;
|
|
1330
1363
|
const installRoot = match[1].trim();
|
|
1331
1364
|
const gitExe = winPath.join(installRoot, "cmd", "git.exe");
|
|
1332
|
-
if (
|
|
1365
|
+
if (existsSync4(gitExe))
|
|
1333
1366
|
return gitExe;
|
|
1334
1367
|
} catch {}
|
|
1335
1368
|
}
|
|
@@ -1397,7 +1430,7 @@ function probeGit() {
|
|
|
1397
1430
|
}
|
|
1398
1431
|
}
|
|
1399
1432
|
for (const candidate of canonicalGitCandidates()) {
|
|
1400
|
-
if (!
|
|
1433
|
+
if (!existsSync4(candidate))
|
|
1401
1434
|
continue;
|
|
1402
1435
|
const verify = tryRun(candidate);
|
|
1403
1436
|
if (verify.ok) {
|
|
@@ -1536,7 +1569,7 @@ async function resolveApp(client, nameOrId, workspaceId) {
|
|
|
1536
1569
|
}
|
|
1537
1570
|
|
|
1538
1571
|
// src/utils/ignore-matcher.ts
|
|
1539
|
-
import { existsSync as
|
|
1572
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
1540
1573
|
import { basename, join as join3 } from "path";
|
|
1541
1574
|
function defaultIgnoreSets() {
|
|
1542
1575
|
return {
|
|
@@ -1578,10 +1611,10 @@ function parseGitignoreContent(content) {
|
|
|
1578
1611
|
}
|
|
1579
1612
|
function loadGitignoreFromDir(dir) {
|
|
1580
1613
|
const path = join3(dir, ".gitignore");
|
|
1581
|
-
if (!
|
|
1614
|
+
if (!existsSync5(path))
|
|
1582
1615
|
return { dirs: new Set, files: new Set };
|
|
1583
1616
|
try {
|
|
1584
|
-
return parseGitignoreContent(
|
|
1617
|
+
return parseGitignoreContent(readFileSync4(path, "utf-8"));
|
|
1585
1618
|
} catch {
|
|
1586
1619
|
return { dirs: new Set, files: new Set };
|
|
1587
1620
|
}
|
|
@@ -1637,7 +1670,7 @@ var init_ignore_matcher = __esm(() => {
|
|
|
1637
1670
|
|
|
1638
1671
|
// src/template/manifest.ts
|
|
1639
1672
|
import { createHash } from "crypto";
|
|
1640
|
-
import { readFileSync as
|
|
1673
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync6, readdirSync, mkdirSync as mkdirSync2 } from "fs";
|
|
1641
1674
|
import { join as join4, relative, sep } from "path";
|
|
1642
1675
|
function sha256(data) {
|
|
1643
1676
|
return "sha256:" + createHash("sha256").update(data).digest("hex");
|
|
@@ -1669,24 +1702,24 @@ async function generateManifest(dir) {
|
|
|
1669
1702
|
const sets = buildIgnoreSets(dir);
|
|
1670
1703
|
const allFiles = walkDir(dir, dir, sets);
|
|
1671
1704
|
for (const relPath of allFiles) {
|
|
1672
|
-
const content =
|
|
1705
|
+
const content = readFileSync5(join4(dir, relPath));
|
|
1673
1706
|
files[relPath] = sha256(content);
|
|
1674
1707
|
}
|
|
1675
1708
|
return { version: 1, files };
|
|
1676
1709
|
}
|
|
1677
1710
|
async function saveManifest(dir, manifest) {
|
|
1678
1711
|
const manifestDir = join4(dir, ".runwork");
|
|
1679
|
-
if (!
|
|
1712
|
+
if (!existsSync6(manifestDir)) {
|
|
1680
1713
|
mkdirSync2(manifestDir, { recursive: true });
|
|
1681
1714
|
}
|
|
1682
1715
|
writeFileSync2(join4(manifestDir, "template-manifest.json"), JSON.stringify(manifest, null, 2));
|
|
1683
1716
|
}
|
|
1684
1717
|
async function loadManifest(dir) {
|
|
1685
1718
|
const manifestPath = join4(dir, ".runwork", "template-manifest.json");
|
|
1686
|
-
if (!
|
|
1719
|
+
if (!existsSync6(manifestPath))
|
|
1687
1720
|
return null;
|
|
1688
1721
|
try {
|
|
1689
|
-
const manifest = JSON.parse(
|
|
1722
|
+
const manifest = JSON.parse(readFileSync5(manifestPath, "utf-8"));
|
|
1690
1723
|
const files = {};
|
|
1691
1724
|
for (const [relPath, hash] of Object.entries(manifest.files)) {
|
|
1692
1725
|
files[relPath.split("\\").join("/")] = hash;
|
|
@@ -1719,7 +1752,7 @@ async function detectUserEdits(dir, manifest) {
|
|
|
1719
1752
|
continue;
|
|
1720
1753
|
const expectedHash = manifest.files[relPath];
|
|
1721
1754
|
if (expectedHash) {
|
|
1722
|
-
const content =
|
|
1755
|
+
const content = readFileSync5(join4(dir, relPath));
|
|
1723
1756
|
if (sha256(content) === expectedHash)
|
|
1724
1757
|
continue;
|
|
1725
1758
|
}
|
|
@@ -1743,7 +1776,7 @@ async function detectUserEdits(dir, manifest) {
|
|
|
1743
1776
|
continue;
|
|
1744
1777
|
const filePath = join4(dir, relPath);
|
|
1745
1778
|
try {
|
|
1746
|
-
const content =
|
|
1779
|
+
const content = readFileSync5(filePath);
|
|
1747
1780
|
if (sha256(content) !== expectedHash) {
|
|
1748
1781
|
edits.push(relPath);
|
|
1749
1782
|
}
|
|
@@ -1757,8 +1790,8 @@ var init_manifest = __esm(() => {
|
|
|
1757
1790
|
});
|
|
1758
1791
|
|
|
1759
1792
|
// src/utils/zip.ts
|
|
1760
|
-
import { mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as
|
|
1761
|
-
import { join as join5, dirname, relative as relative2, sep as sep2 } from "path";
|
|
1793
|
+
import { mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync6, statSync, writeFileSync as writeFileSync3 } from "fs";
|
|
1794
|
+
import { join as join5, dirname as dirname2, relative as relative2, sep as sep2 } from "path";
|
|
1762
1795
|
import { unzipSync, zipSync } from "fflate";
|
|
1763
1796
|
function ensureDirSync(dir) {
|
|
1764
1797
|
try {
|
|
@@ -1776,7 +1809,7 @@ function extractZip(zipData, targetDir) {
|
|
|
1776
1809
|
ensureDirSync(fullPath);
|
|
1777
1810
|
continue;
|
|
1778
1811
|
}
|
|
1779
|
-
ensureDirSync(
|
|
1812
|
+
ensureDirSync(dirname2(fullPath));
|
|
1780
1813
|
writeFileSync3(fullPath, data);
|
|
1781
1814
|
}
|
|
1782
1815
|
}
|
|
@@ -1790,13 +1823,13 @@ function createZipFromDir(sourceDir, outputPath) {
|
|
|
1790
1823
|
walk(absPath);
|
|
1791
1824
|
} else if (stats.isFile()) {
|
|
1792
1825
|
const relPath = relative2(sourceDir, absPath).split(sep2).join("/");
|
|
1793
|
-
files[relPath] =
|
|
1826
|
+
files[relPath] = readFileSync6(absPath);
|
|
1794
1827
|
}
|
|
1795
1828
|
}
|
|
1796
1829
|
};
|
|
1797
1830
|
walk(sourceDir);
|
|
1798
1831
|
const zipped = zipSync(files);
|
|
1799
|
-
ensureDirSync(
|
|
1832
|
+
ensureDirSync(dirname2(outputPath));
|
|
1800
1833
|
writeFileSync3(outputPath, zipped);
|
|
1801
1834
|
}
|
|
1802
1835
|
var init_zip = () => {};
|
|
@@ -2092,18 +2125,18 @@ __export(exports_init, {
|
|
|
2092
2125
|
DEFAULT_APPS_DIR: () => DEFAULT_APPS_DIR
|
|
2093
2126
|
});
|
|
2094
2127
|
import { Command as Command2 } from "commander";
|
|
2095
|
-
import { writeFileSync as writeFileSync4, existsSync as
|
|
2128
|
+
import { writeFileSync as writeFileSync4, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
|
|
2096
2129
|
import { join as join7, resolve } from "path";
|
|
2097
2130
|
import { homedir as homedir4 } from "os";
|
|
2098
2131
|
async function execInit(client, appName, workspace, options = {}, creds) {
|
|
2099
2132
|
const app = await client.initApp(workspace.id, appName);
|
|
2100
2133
|
const slug = app.slug || appName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
2101
2134
|
const parentDir = options.here ? process.cwd() : DEFAULT_APPS_DIR;
|
|
2102
|
-
if (!options.here && !
|
|
2135
|
+
if (!options.here && !existsSync7(parentDir)) {
|
|
2103
2136
|
mkdirSync4(parentDir, { recursive: true });
|
|
2104
2137
|
}
|
|
2105
2138
|
const dir = join7(parentDir, slug);
|
|
2106
|
-
if (
|
|
2139
|
+
if (existsSync7(dir)) {
|
|
2107
2140
|
console.error(`Directory "${dir}" already exists.`);
|
|
2108
2141
|
process.exit(1);
|
|
2109
2142
|
}
|
|
@@ -2130,7 +2163,7 @@ async function execInit(client, appName, workspace, options = {}, creds) {
|
|
|
2130
2163
|
appName: app.name
|
|
2131
2164
|
};
|
|
2132
2165
|
writeFileSync4(join7(dir, ".runwork.json"), JSON.stringify(config, null, 2));
|
|
2133
|
-
if (!
|
|
2166
|
+
if (!existsSync7(join7(dir, ".git"))) {
|
|
2134
2167
|
try {
|
|
2135
2168
|
execFileSync("git", ["init"], { cwd: dir, stdio: "pipe" });
|
|
2136
2169
|
} catch (err) {
|
|
@@ -2148,7 +2181,7 @@ async function execInit(client, appName, workspace, options = {}, creds) {
|
|
|
2148
2181
|
} catch {
|
|
2149
2182
|
execFileSync("git", ["remote", "set-url", "runwork", remoteUrl], { cwd: dir, stdio: "pipe" });
|
|
2150
2183
|
}
|
|
2151
|
-
if (!
|
|
2184
|
+
if (!existsSync7(join7(dir, ".gitignore"))) {
|
|
2152
2185
|
writeFileSync4(join7(dir, ".gitignore"), `node_modules/
|
|
2153
2186
|
.runwork/
|
|
2154
2187
|
.dev.vars
|
|
@@ -2401,7 +2434,7 @@ __export(exports_clone, {
|
|
|
2401
2434
|
RESTRICTED_FS_HELP: () => RESTRICTED_FS_HELP
|
|
2402
2435
|
});
|
|
2403
2436
|
import { Command as Command3 } from "commander";
|
|
2404
|
-
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as
|
|
2437
|
+
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync8 } from "fs";
|
|
2405
2438
|
import { join as join8, resolve as resolve2 } from "path";
|
|
2406
2439
|
async function execClone(client, app, directory, creds) {
|
|
2407
2440
|
const slug = app.slug || app.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
@@ -2419,7 +2452,7 @@ async function execClone(client, app, directory, creds) {
|
|
|
2419
2452
|
}
|
|
2420
2453
|
const manifest = await generateManifest(dir);
|
|
2421
2454
|
await saveManifest(dir, manifest);
|
|
2422
|
-
if (!
|
|
2455
|
+
if (!existsSync8(join8(dir, ".git"))) {
|
|
2423
2456
|
try {
|
|
2424
2457
|
execFileSync("git", ["init"], { cwd: dir, stdio: "pipe" });
|
|
2425
2458
|
} catch (err) {
|
|
@@ -2592,7 +2625,7 @@ App "${cloneResult.appName}" cloned to ${cloneResult.directory}`);
|
|
|
2592
2625
|
});
|
|
2593
2626
|
|
|
2594
2627
|
// src/git/auto-commit.ts
|
|
2595
|
-
import { readFileSync as
|
|
2628
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
2596
2629
|
import { watch } from "chokidar";
|
|
2597
2630
|
import { join as join9, relative as relative4 } from "path";
|
|
2598
2631
|
async function watchAndAutoCommit(directory, client, appId, callbacks) {
|
|
@@ -2654,7 +2687,7 @@ async function executeFastSync(directory, client, appId) {
|
|
|
2654
2687
|
if (BINARY_EXTENSIONS.has(ext.toLowerCase()))
|
|
2655
2688
|
continue;
|
|
2656
2689
|
try {
|
|
2657
|
-
const contents =
|
|
2690
|
+
const contents = readFileSync7(join9(directory, filePath), "utf-8");
|
|
2658
2691
|
files.push({ filePath, fileContents: contents });
|
|
2659
2692
|
} catch {}
|
|
2660
2693
|
}
|
|
@@ -2948,16 +2981,16 @@ var init_sync = __esm(() => {
|
|
|
2948
2981
|
});
|
|
2949
2982
|
|
|
2950
2983
|
// src/git/critical-files.ts
|
|
2951
|
-
import { readFileSync as
|
|
2984
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync9 } from "fs";
|
|
2952
2985
|
import { join as join11 } from "path";
|
|
2953
2986
|
function snapshotCriticalFiles(cwd) {
|
|
2954
2987
|
const snapshots = [];
|
|
2955
2988
|
for (const rel of CRITICAL_FILES) {
|
|
2956
2989
|
const abs = join11(cwd, rel);
|
|
2957
|
-
if (!
|
|
2990
|
+
if (!existsSync9(abs))
|
|
2958
2991
|
continue;
|
|
2959
2992
|
try {
|
|
2960
|
-
snapshots.push({ path: rel, contents:
|
|
2993
|
+
snapshots.push({ path: rel, contents: readFileSync8(abs, "utf-8") });
|
|
2961
2994
|
} catch {}
|
|
2962
2995
|
}
|
|
2963
2996
|
return snapshots;
|
|
@@ -2966,7 +2999,7 @@ function restoreMissingCriticalFiles(cwd, snapshots) {
|
|
|
2966
2999
|
const restored = [];
|
|
2967
3000
|
for (const snap of snapshots) {
|
|
2968
3001
|
const abs = join11(cwd, snap.path);
|
|
2969
|
-
if (
|
|
3002
|
+
if (existsSync9(abs))
|
|
2970
3003
|
continue;
|
|
2971
3004
|
try {
|
|
2972
3005
|
writeFileSync6(abs, snap.contents, "utf-8");
|
|
@@ -3009,7 +3042,7 @@ function buildStartupSyncSummary(input) {
|
|
|
3009
3042
|
|
|
3010
3043
|
// src/logs/tailer.ts
|
|
3011
3044
|
import { appendFileSync, mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
3012
|
-
import { join as join12, dirname as
|
|
3045
|
+
import { join as join12, dirname as dirname3 } from "path";
|
|
3013
3046
|
function formatTime() {
|
|
3014
3047
|
const now = new Date;
|
|
3015
3048
|
return [
|
|
@@ -3059,7 +3092,7 @@ function startLogTailer(options) {
|
|
|
3059
3092
|
} = options;
|
|
3060
3093
|
const logFilePath = join12(projectDir, LOG_FILE);
|
|
3061
3094
|
if (toFile) {
|
|
3062
|
-
mkdirSync6(
|
|
3095
|
+
mkdirSync6(dirname3(logFilePath), { recursive: true });
|
|
3063
3096
|
writeFileSync7(logFilePath, `# Runwork dev logs - started ${new Date().toISOString()}
|
|
3064
3097
|
|
|
3065
3098
|
`, "utf-8");
|
|
@@ -7469,13 +7502,13 @@ export {};
|
|
|
7469
7502
|
});
|
|
7470
7503
|
|
|
7471
7504
|
// src/types-manager.ts
|
|
7472
|
-
import { existsSync as
|
|
7505
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync9, readdirSync as readdirSync4, copyFileSync, writeFileSync as writeFileSync9 } from "fs";
|
|
7473
7506
|
import { join as join14 } from "path";
|
|
7474
7507
|
async function populateTypes(projectDir) {
|
|
7475
7508
|
const typesDir = join14(projectDir, TYPES_DIR);
|
|
7476
7509
|
mkdirSync9(typesDir, { recursive: true });
|
|
7477
7510
|
const frameworkDist = join14(projectDir, "node_modules/@runworkai/framework/dist");
|
|
7478
|
-
if (
|
|
7511
|
+
if (existsSync14(frameworkDist)) {
|
|
7479
7512
|
copyDtsFiles(frameworkDist, typesDir);
|
|
7480
7513
|
console.log("Types populated from node_modules/@runworkai/framework");
|
|
7481
7514
|
return;
|
|
@@ -7612,7 +7645,7 @@ function createKeyboardListener() {
|
|
|
7612
7645
|
}
|
|
7613
7646
|
|
|
7614
7647
|
// src/generated/version.ts
|
|
7615
|
-
var VERSION = "0.
|
|
7648
|
+
var VERSION = "0.20.0";
|
|
7616
7649
|
|
|
7617
7650
|
// src/commands/dev.ts
|
|
7618
7651
|
var exports_dev = {};
|
|
@@ -7621,7 +7654,7 @@ __export(exports_dev, {
|
|
|
7621
7654
|
devCommand: () => devCommand
|
|
7622
7655
|
});
|
|
7623
7656
|
import { Command as Command4, Option } from "commander";
|
|
7624
|
-
import { readFileSync as
|
|
7657
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync10, existsSync as existsSync15 } from "fs";
|
|
7625
7658
|
import { join as join15 } from "path";
|
|
7626
7659
|
async function populateSkill(projectDir, client, appId) {
|
|
7627
7660
|
try {
|
|
@@ -7632,11 +7665,11 @@ async function populateSkill(projectDir, client, appId) {
|
|
|
7632
7665
|
} catch {}
|
|
7633
7666
|
}
|
|
7634
7667
|
function readConfig() {
|
|
7635
|
-
if (!
|
|
7668
|
+
if (!existsSync15(".runwork.json")) {
|
|
7636
7669
|
console.error("No .runwork.json found. Run `runwork init` first.");
|
|
7637
7670
|
process.exit(1);
|
|
7638
7671
|
}
|
|
7639
|
-
return JSON.parse(
|
|
7672
|
+
return JSON.parse(readFileSync12(".runwork.json", "utf-8"));
|
|
7640
7673
|
}
|
|
7641
7674
|
async function execDev(options) {
|
|
7642
7675
|
const useJson = options?.json ?? false;
|
|
@@ -8516,7 +8549,7 @@ var exports_welcome = {};
|
|
|
8516
8549
|
__export(exports_welcome, {
|
|
8517
8550
|
runWelcomeWizard: () => runWelcomeWizard
|
|
8518
8551
|
});
|
|
8519
|
-
import { basename as
|
|
8552
|
+
import { basename as basename4 } from "path";
|
|
8520
8553
|
async function runWelcomeWizard() {
|
|
8521
8554
|
console.log(getWelcomeBanner());
|
|
8522
8555
|
let creds = getCredentials();
|
|
@@ -8582,7 +8615,7 @@ async function runWelcomeWizard() {
|
|
|
8582
8615
|
const { execDev: execDev2 } = await Promise.resolve().then(() => (init_dev(), exports_dev));
|
|
8583
8616
|
await execDev2();
|
|
8584
8617
|
} else {
|
|
8585
|
-
const slug =
|
|
8618
|
+
const slug = basename4(appDir) || appDir;
|
|
8586
8619
|
console.log("");
|
|
8587
8620
|
console.log(`Next: ${cyan(`cd ${slug} && runwork dev`)}`);
|
|
8588
8621
|
}
|
|
@@ -8597,13 +8630,13 @@ var init_welcome = __esm(() => {
|
|
|
8597
8630
|
});
|
|
8598
8631
|
|
|
8599
8632
|
// src/index.ts
|
|
8600
|
-
import { Command as
|
|
8633
|
+
import { Command as Command37 } from "commander";
|
|
8601
8634
|
|
|
8602
8635
|
// src/commands/login.ts
|
|
8603
8636
|
init_login_flow();
|
|
8604
8637
|
init_colors();
|
|
8605
8638
|
import { Command } from "commander";
|
|
8606
|
-
var loginCommand = new Command("login").description("Authenticate with Runwork platform").argument("[url]", "Login URL from a previous --no-open session").option("--base-url <url>", "Platform URL", "https://runwork.ai").option("--no-open", "Print login URL without opening browser (polls for completion)").option("--print-only", "With --no-open: print URL and exit without polling").option("--api-key <key>", "Authenticate directly with an API key (for CI)").action(async (url, options) => {
|
|
8639
|
+
var loginCommand = new Command("login").description("Authenticate with Runwork platform").argument("[url]", "Login URL from a previous --no-open session").option("--base-url <url>", "Platform URL", "https://runwork.ai").option("--no-open", "Print login URL without opening browser (polls for completion)").option("--print-only", "With --no-open: print URL and exit without polling").option("--api-key <key>", "Authenticate directly with an API key (for CI)").option("--register", "Open the browser on the registration form for a new account").option("--provider <provider>", "Start this OAuth provider flow directly (google, github)").action(async (url, options) => {
|
|
8607
8640
|
if (!options)
|
|
8608
8641
|
return;
|
|
8609
8642
|
if (options.apiKey) {
|
|
@@ -8621,7 +8654,7 @@ var loginCommand = new Command("login").description("Authenticate with Runwork p
|
|
|
8621
8654
|
printNextSteps();
|
|
8622
8655
|
return;
|
|
8623
8656
|
}
|
|
8624
|
-
await performLogin(options.baseUrl);
|
|
8657
|
+
await performLogin(options.baseUrl, { register: options.register, provider: options.provider });
|
|
8625
8658
|
printNextSteps();
|
|
8626
8659
|
});
|
|
8627
8660
|
function printNextSteps() {
|
|
@@ -8644,12 +8677,12 @@ init_store();
|
|
|
8644
8677
|
init_client();
|
|
8645
8678
|
init_colors();
|
|
8646
8679
|
import { Command as Command5, Option as Option2 } from "commander";
|
|
8647
|
-
import { readFileSync as
|
|
8680
|
+
import { readFileSync as readFileSync15, existsSync as existsSync18 } from "fs";
|
|
8648
8681
|
|
|
8649
8682
|
// src/deploy/deploy-state.ts
|
|
8650
8683
|
init_subprocess();
|
|
8651
|
-
import { existsSync as
|
|
8652
|
-
import { dirname as
|
|
8684
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync10, readFileSync as readFileSync13, writeFileSync as writeFileSync11 } from "fs";
|
|
8685
|
+
import { dirname as dirname4, join as join16 } from "path";
|
|
8653
8686
|
function deployStatePath(cwd) {
|
|
8654
8687
|
return join16(cwd, ".runwork", "last-deploy.json");
|
|
8655
8688
|
}
|
|
@@ -8663,17 +8696,17 @@ function getHeadSha(cwd) {
|
|
|
8663
8696
|
function writeDeployState(cwd, state) {
|
|
8664
8697
|
const path2 = deployStatePath(cwd);
|
|
8665
8698
|
try {
|
|
8666
|
-
if (!
|
|
8667
|
-
mkdirSync10(
|
|
8699
|
+
if (!existsSync16(dirname4(path2)))
|
|
8700
|
+
mkdirSync10(dirname4(path2), { recursive: true });
|
|
8668
8701
|
writeFileSync11(path2, JSON.stringify(state, null, 2));
|
|
8669
8702
|
} catch {}
|
|
8670
8703
|
}
|
|
8671
8704
|
function readDeployState(cwd) {
|
|
8672
8705
|
const path2 = deployStatePath(cwd);
|
|
8673
|
-
if (!
|
|
8706
|
+
if (!existsSync16(path2))
|
|
8674
8707
|
return null;
|
|
8675
8708
|
try {
|
|
8676
|
-
const parsed = JSON.parse(
|
|
8709
|
+
const parsed = JSON.parse(readFileSync13(path2, "utf-8"));
|
|
8677
8710
|
if (typeof parsed.sha === "string" && typeof parsed.deployedAt === "string") {
|
|
8678
8711
|
return { sha: parsed.sha, deployedAt: parsed.deployedAt, url: parsed.url ?? "" };
|
|
8679
8712
|
}
|
|
@@ -8701,8 +8734,8 @@ function getDeploySummary(cwd) {
|
|
|
8701
8734
|
|
|
8702
8735
|
// src/deploy/deploy-status.ts
|
|
8703
8736
|
init_session();
|
|
8704
|
-
import { existsSync as
|
|
8705
|
-
import { dirname as
|
|
8737
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync11, readFileSync as readFileSync14, writeFileSync as writeFileSync12 } from "fs";
|
|
8738
|
+
import { dirname as dirname5, join as join17 } from "path";
|
|
8706
8739
|
function evaluateDeployStatus(status, deps = {}) {
|
|
8707
8740
|
if (status.state !== "in-progress") {
|
|
8708
8741
|
return { status, effectiveState: status.state };
|
|
@@ -8726,17 +8759,17 @@ function deployLogPath(cwd) {
|
|
|
8726
8759
|
function writeDeployStatus(cwd, status) {
|
|
8727
8760
|
const path2 = statusPath(cwd);
|
|
8728
8761
|
try {
|
|
8729
|
-
if (!
|
|
8730
|
-
mkdirSync11(
|
|
8762
|
+
if (!existsSync17(dirname5(path2)))
|
|
8763
|
+
mkdirSync11(dirname5(path2), { recursive: true });
|
|
8731
8764
|
writeFileSync12(path2, JSON.stringify(status, null, 2));
|
|
8732
8765
|
} catch {}
|
|
8733
8766
|
}
|
|
8734
8767
|
function readDeployStatus(cwd) {
|
|
8735
8768
|
const path2 = statusPath(cwd);
|
|
8736
|
-
if (!
|
|
8769
|
+
if (!existsSync17(path2))
|
|
8737
8770
|
return null;
|
|
8738
8771
|
try {
|
|
8739
|
-
const parsed = JSON.parse(
|
|
8772
|
+
const parsed = JSON.parse(readFileSync14(path2, "utf-8"));
|
|
8740
8773
|
if ((parsed.state === "in-progress" || parsed.state === "succeeded" || parsed.state === "failed") && typeof parsed.startedAt === "string") {
|
|
8741
8774
|
return parsed;
|
|
8742
8775
|
}
|
|
@@ -8842,7 +8875,7 @@ init_prompt();
|
|
|
8842
8875
|
var deployCommand = new Command5("deploy").description("Deploy the current app to production").option("-y, --yes", "Skip the confirmation prompt when the working tree differs from the preview").option("--detach", "Run the deploy in the background and return immediately. Logs go to .runwork/deploy-{stdout,stderr}.log; poll `runwork deploy --status`.").option("--status", "Show the status (in-progress/succeeded/failed) of the last deploy started from this machine, plus the deployed-vs-local commit.").addOption(new Option2(DEPLOY_DETACHED_CHILD_FLAG).hideHelp()).action(async (opts, command) => {
|
|
8843
8876
|
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
8844
8877
|
const cwd = process.cwd();
|
|
8845
|
-
if (!
|
|
8878
|
+
if (!existsSync18(".runwork.json")) {
|
|
8846
8879
|
if (useJson) {
|
|
8847
8880
|
jsonOut(buildErrorResponse("deploy", "No .runwork.json found", "This directory is not a Runwork app.", ["Run runwork init to create an app first", "Or cd into an existing app directory"]));
|
|
8848
8881
|
process.exit(1);
|
|
@@ -8855,7 +8888,7 @@ var deployCommand = new Command5("deploy").description("Deploy the current app t
|
|
|
8855
8888
|
return;
|
|
8856
8889
|
}
|
|
8857
8890
|
requireGit("deploy");
|
|
8858
|
-
const config = JSON.parse(
|
|
8891
|
+
const config = JSON.parse(readFileSync15(".runwork.json", "utf-8"));
|
|
8859
8892
|
const isChild = isInternalDeployChild(process.argv);
|
|
8860
8893
|
if (opts.detach && !isChild) {
|
|
8861
8894
|
const startedAt = new Date().toISOString();
|
|
@@ -9068,7 +9101,7 @@ init_client();
|
|
|
9068
9101
|
init_colors();
|
|
9069
9102
|
init_preflight();
|
|
9070
9103
|
import { Command as Command6 } from "commander";
|
|
9071
|
-
import { readFileSync as
|
|
9104
|
+
import { readFileSync as readFileSync16, existsSync as existsSync19 } from "fs";
|
|
9072
9105
|
|
|
9073
9106
|
// src/validate/freshness.ts
|
|
9074
9107
|
init_subprocess();
|
|
@@ -9140,7 +9173,7 @@ var BUILD_FOLLOWUP = "In-sandbox typecheck/lint requires a worker exec endpoint
|
|
|
9140
9173
|
var validateCommand = new Command6("validate").description("Validate the app before deploy: git sync state + preview liveness (no local deps needed)").action(async (_opts, command) => {
|
|
9141
9174
|
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
9142
9175
|
const cwd = process.cwd();
|
|
9143
|
-
if (!
|
|
9176
|
+
if (!existsSync19(".runwork.json")) {
|
|
9144
9177
|
if (useJson) {
|
|
9145
9178
|
jsonOut({ success: false, command: "validate", error: "No .runwork.json found. Run inside a Runwork app." });
|
|
9146
9179
|
process.exit(1);
|
|
@@ -9149,7 +9182,7 @@ var validateCommand = new Command6("validate").description("Validate the app bef
|
|
|
9149
9182
|
process.exit(1);
|
|
9150
9183
|
}
|
|
9151
9184
|
requireGit("validate");
|
|
9152
|
-
const config = JSON.parse(
|
|
9185
|
+
const config = JSON.parse(readFileSync16(".runwork.json", "utf-8"));
|
|
9153
9186
|
const creds = requireAuth();
|
|
9154
9187
|
const client = new ApiClient(creds);
|
|
9155
9188
|
const sync = getGitFreshness(cwd);
|
|
@@ -9205,13 +9238,13 @@ var validateCommand = new Command6("validate").description("Validate the app bef
|
|
|
9205
9238
|
init_store();
|
|
9206
9239
|
init_client();
|
|
9207
9240
|
import { Command as Command7 } from "commander";
|
|
9208
|
-
import { readFileSync as
|
|
9241
|
+
import { readFileSync as readFileSync17, existsSync as existsSync20 } from "fs";
|
|
9209
9242
|
function readConfig2() {
|
|
9210
|
-
if (!
|
|
9243
|
+
if (!existsSync20(".runwork.json")) {
|
|
9211
9244
|
console.error("No .runwork.json found. Run `runwork init` first.");
|
|
9212
9245
|
process.exit(1);
|
|
9213
9246
|
}
|
|
9214
|
-
return JSON.parse(
|
|
9247
|
+
return JSON.parse(readFileSync17(".runwork.json", "utf-8"));
|
|
9215
9248
|
}
|
|
9216
9249
|
function formatEvent(event) {
|
|
9217
9250
|
const time = new Date(event.timestamp).toLocaleTimeString();
|
|
@@ -9632,12 +9665,12 @@ var logoutCommand = new Command9("logout").description("Remove stored Runwork cr
|
|
|
9632
9665
|
init_store();
|
|
9633
9666
|
init_client();
|
|
9634
9667
|
import { Command as Command10 } from "commander";
|
|
9635
|
-
import { readFileSync as
|
|
9668
|
+
import { readFileSync as readFileSync19, existsSync as existsSync22 } from "fs";
|
|
9636
9669
|
|
|
9637
9670
|
// src/workspace/resolve.ts
|
|
9638
9671
|
init_store();
|
|
9639
9672
|
init_prompt();
|
|
9640
|
-
import { existsSync as
|
|
9673
|
+
import { existsSync as existsSync21, readFileSync as readFileSync18 } from "fs";
|
|
9641
9674
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
9642
9675
|
async function resolveWorkspace2(client, options = {}) {
|
|
9643
9676
|
if (options.workspace) {
|
|
@@ -9650,9 +9683,9 @@ async function resolveWorkspace2(client, options = {}) {
|
|
|
9650
9683
|
if (process.env.WORKSPACE_ID) {
|
|
9651
9684
|
return { workspaceId: process.env.WORKSPACE_ID, workspaceName: "", source: "flag" };
|
|
9652
9685
|
}
|
|
9653
|
-
if (
|
|
9686
|
+
if (existsSync21(".runwork.json")) {
|
|
9654
9687
|
try {
|
|
9655
|
-
const config = JSON.parse(
|
|
9688
|
+
const config = JSON.parse(readFileSync18(".runwork.json", "utf-8"));
|
|
9656
9689
|
if (config.workspaceId) {
|
|
9657
9690
|
return {
|
|
9658
9691
|
workspaceId: config.workspaceId,
|
|
@@ -9697,7 +9730,7 @@ function saveDefaultWorkspace(workspaceId, workspaceName) {
|
|
|
9697
9730
|
}
|
|
9698
9731
|
}
|
|
9699
9732
|
function hasProjectConfig() {
|
|
9700
|
-
return
|
|
9733
|
+
return existsSync21(".runwork.json");
|
|
9701
9734
|
}
|
|
9702
9735
|
async function resolveApp2(client, workspaceId, options = {}) {
|
|
9703
9736
|
if (options.app) {
|
|
@@ -9709,9 +9742,9 @@ async function resolveApp2(client, workspaceId, options = {}) {
|
|
|
9709
9742
|
}
|
|
9710
9743
|
return { appId: match.id, appName: match.name, source: "flag" };
|
|
9711
9744
|
}
|
|
9712
|
-
if (
|
|
9745
|
+
if (existsSync21(".runwork.json")) {
|
|
9713
9746
|
try {
|
|
9714
|
-
const config = JSON.parse(
|
|
9747
|
+
const config = JSON.parse(readFileSync18(".runwork.json", "utf-8"));
|
|
9715
9748
|
if (config.appId) {
|
|
9716
9749
|
return { appId: config.appId, appName: config.appName || "", source: "project" };
|
|
9717
9750
|
}
|
|
@@ -10044,11 +10077,11 @@ var callCommand = new Command10("call").description("Make a proxy call to a conn
|
|
|
10044
10077
|
let curlStr = opts.curl;
|
|
10045
10078
|
if (opts.curlFile) {
|
|
10046
10079
|
const filePath = opts.curlFile;
|
|
10047
|
-
if (!
|
|
10080
|
+
if (!existsSync22(filePath)) {
|
|
10048
10081
|
console.error(`File not found: ${filePath}`);
|
|
10049
10082
|
process.exit(1);
|
|
10050
10083
|
}
|
|
10051
|
-
curlStr =
|
|
10084
|
+
curlStr = readFileSync19(filePath, "utf-8");
|
|
10052
10085
|
}
|
|
10053
10086
|
try {
|
|
10054
10087
|
const result = await parseCurlToRequest(curlStr);
|
|
@@ -10111,14 +10144,14 @@ init_store();
|
|
|
10111
10144
|
init_client();
|
|
10112
10145
|
init_colors();
|
|
10113
10146
|
import { Command as Command11 } from "commander";
|
|
10114
|
-
import { readFileSync as
|
|
10147
|
+
import { readFileSync as readFileSync20, existsSync as existsSync23 } from "fs";
|
|
10115
10148
|
var openCommand = new Command11("open").description("Open app preview or dashboard in browser").argument("[target]", "What to open: preview (default), dashboard", "preview").action(async (target, _opts, command) => {
|
|
10116
10149
|
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
10117
|
-
if (!
|
|
10150
|
+
if (!existsSync23(".runwork.json")) {
|
|
10118
10151
|
console.error("No .runwork.json found. Run `runwork init` first.");
|
|
10119
10152
|
process.exit(1);
|
|
10120
10153
|
}
|
|
10121
|
-
const config = JSON.parse(
|
|
10154
|
+
const config = JSON.parse(readFileSync20(".runwork.json", "utf-8"));
|
|
10122
10155
|
const creds = requireAuth();
|
|
10123
10156
|
const client = new ApiClient(creds);
|
|
10124
10157
|
const open = await import("open");
|
|
@@ -10159,7 +10192,7 @@ var openCommand = new Command11("open").description("Open app preview or dashboa
|
|
|
10159
10192
|
// src/commands/info.ts
|
|
10160
10193
|
init_agent_guidance();
|
|
10161
10194
|
import { Command as Command12 } from "commander";
|
|
10162
|
-
import { readFileSync as
|
|
10195
|
+
import { readFileSync as readFileSync21, existsSync as existsSync24 } from "fs";
|
|
10163
10196
|
import { join as join20 } from "path";
|
|
10164
10197
|
|
|
10165
10198
|
// src/utils/app-info.ts
|
|
@@ -10351,20 +10384,20 @@ function readLocalDevSession(appDir, appId) {
|
|
|
10351
10384
|
};
|
|
10352
10385
|
}
|
|
10353
10386
|
function tryReadConfig() {
|
|
10354
|
-
if (!
|
|
10387
|
+
if (!existsSync24(".runwork.json"))
|
|
10355
10388
|
return null;
|
|
10356
10389
|
try {
|
|
10357
|
-
return JSON.parse(
|
|
10390
|
+
return JSON.parse(readFileSync21(".runwork.json", "utf-8"));
|
|
10358
10391
|
} catch {
|
|
10359
10392
|
return null;
|
|
10360
10393
|
}
|
|
10361
10394
|
}
|
|
10362
10395
|
function readBlueprint(cwd) {
|
|
10363
10396
|
const blueprintPath = join20(cwd, "blueprint.json");
|
|
10364
|
-
if (!
|
|
10397
|
+
if (!existsSync24(blueprintPath))
|
|
10365
10398
|
return null;
|
|
10366
10399
|
try {
|
|
10367
|
-
return JSON.parse(
|
|
10400
|
+
return JSON.parse(readFileSync21(blueprintPath, "utf-8"));
|
|
10368
10401
|
} catch {
|
|
10369
10402
|
return null;
|
|
10370
10403
|
}
|
|
@@ -10708,7 +10741,7 @@ var infoCommand = new Command12("info").description("Show app context, registrie
|
|
|
10708
10741
|
init_store();
|
|
10709
10742
|
init_client();
|
|
10710
10743
|
import { Command as Command13 } from "commander";
|
|
10711
|
-
import { readFileSync as
|
|
10744
|
+
import { readFileSync as readFileSync22, existsSync as existsSync25 } from "fs";
|
|
10712
10745
|
function truncate(text2, max) {
|
|
10713
10746
|
if (!text2)
|
|
10714
10747
|
return "";
|
|
@@ -10786,7 +10819,7 @@ var pushCommand = new Command13("push").description("Upload a local skill file t
|
|
|
10786
10819
|
nameArg = first;
|
|
10787
10820
|
filePath = second;
|
|
10788
10821
|
} else if (first) {
|
|
10789
|
-
if (
|
|
10822
|
+
if (existsSync25(first)) {
|
|
10790
10823
|
filePath = first;
|
|
10791
10824
|
} else if (!process.stdin.isTTY) {
|
|
10792
10825
|
nameArg = first;
|
|
@@ -10803,11 +10836,11 @@ var pushCommand = new Command13("push").description("Upload a local skill file t
|
|
|
10803
10836
|
}
|
|
10804
10837
|
let content;
|
|
10805
10838
|
if (filePath) {
|
|
10806
|
-
if (!
|
|
10839
|
+
if (!existsSync25(filePath)) {
|
|
10807
10840
|
console.error(`File not found: ${filePath}`);
|
|
10808
10841
|
process.exit(1);
|
|
10809
10842
|
}
|
|
10810
|
-
content =
|
|
10843
|
+
content = readFileSync22(filePath, "utf-8");
|
|
10811
10844
|
} else {
|
|
10812
10845
|
content = await readStdin2();
|
|
10813
10846
|
if (!content.trim()) {
|
|
@@ -11019,8 +11052,8 @@ var skillsCommand = new Command13("skills").description("Manage workspace skills
|
|
|
11019
11052
|
// src/commands/reflect.ts
|
|
11020
11053
|
init_subprocess();
|
|
11021
11054
|
import { Command as Command14 } from "commander";
|
|
11022
|
-
import { writeFileSync as
|
|
11023
|
-
import { join as
|
|
11055
|
+
import { writeFileSync as writeFileSync25, mkdirSync as mkdirSync25 } from "fs";
|
|
11056
|
+
import { join as join37 } from "path";
|
|
11024
11057
|
|
|
11025
11058
|
// src/utils/which.ts
|
|
11026
11059
|
init_subprocess();
|
|
@@ -11058,9 +11091,9 @@ init_client();
|
|
|
11058
11091
|
|
|
11059
11092
|
// src/agents/claude-code.ts
|
|
11060
11093
|
init_subprocess();
|
|
11061
|
-
import { chmodSync, existsSync as
|
|
11062
|
-
import { join as
|
|
11063
|
-
import { homedir as
|
|
11094
|
+
import { chmodSync, existsSync as existsSync30, mkdirSync as mkdirSync16, readFileSync as readFileSync25, readdirSync as readdirSync7, rmSync as rmSync5, statSync as statSync4, writeFileSync as writeFileSync16 } from "fs";
|
|
11095
|
+
import { join as join23 } from "path";
|
|
11096
|
+
import { homedir as homedir6, platform as platform2 } from "os";
|
|
11064
11097
|
|
|
11065
11098
|
// ../../shared/skill/skill-canonical.ts
|
|
11066
11099
|
function toSkillSlug(value) {
|
|
@@ -11169,6 +11202,10 @@ function buildSkillMd(parts) {
|
|
|
11169
11202
|
var RUNWORK_MCP_PREFIX = "Runwork: ";
|
|
11170
11203
|
var RUNWORK_MCP_PREFIX_LEGACY = "runwork-";
|
|
11171
11204
|
var RUNWORK_WORKSPACE_MCP_NAME = "Runwork";
|
|
11205
|
+
var RUNWORK_PLUGIN_MARKETPLACE = "runwork";
|
|
11206
|
+
function isRunworkPluginKey(key) {
|
|
11207
|
+
return key.endsWith(`@${RUNWORK_PLUGIN_MARKETPLACE}`);
|
|
11208
|
+
}
|
|
11172
11209
|
function appendTokenToUrl(url, token) {
|
|
11173
11210
|
const parsed = new URL(url);
|
|
11174
11211
|
parsed.searchParams.set("token", token);
|
|
@@ -11504,9 +11541,70 @@ function skillNameFromPath(path2) {
|
|
|
11504
11541
|
return canonical || null;
|
|
11505
11542
|
}
|
|
11506
11543
|
|
|
11544
|
+
// src/utils/trash.ts
|
|
11545
|
+
init_atomic_json();
|
|
11546
|
+
import { cpSync, existsSync as existsSync26, mkdirSync as mkdirSync13, readdirSync as readdirSync5, renameSync as renameSync3, rmSync as rmSync4, statSync as statSync3 } from "fs";
|
|
11547
|
+
import { basename as basename2, dirname as dirname6, join as join21 } from "path";
|
|
11548
|
+
import { homedir as homedir5 } from "os";
|
|
11549
|
+
var TRASH_RETENTION_DAYS = 30;
|
|
11550
|
+
function trashRoot() {
|
|
11551
|
+
return join21(homedir5(), ".runwork", "trash");
|
|
11552
|
+
}
|
|
11553
|
+
function batchDir(now) {
|
|
11554
|
+
const stamp = now.toISOString().replace(/[:.]/g, "-");
|
|
11555
|
+
return join21(trashRoot(), `${stamp}-${process.pid}`);
|
|
11556
|
+
}
|
|
11557
|
+
function pruneTrash(now = new Date) {
|
|
11558
|
+
const root = trashRoot();
|
|
11559
|
+
if (!existsSync26(root))
|
|
11560
|
+
return;
|
|
11561
|
+
const cutoff = now.getTime() - TRASH_RETENTION_DAYS * 24 * 60 * 60 * 1000;
|
|
11562
|
+
for (const entry of readdirSync5(root)) {
|
|
11563
|
+
const dir = join21(root, entry);
|
|
11564
|
+
try {
|
|
11565
|
+
if (statSync3(dir).mtimeMs < cutoff)
|
|
11566
|
+
rmSync4(dir, { recursive: true, force: true });
|
|
11567
|
+
} catch {}
|
|
11568
|
+
}
|
|
11569
|
+
}
|
|
11570
|
+
var activeBatch = null;
|
|
11571
|
+
function moveToTrash(sourcePath, reason, now = new Date) {
|
|
11572
|
+
if (!existsSync26(sourcePath))
|
|
11573
|
+
return null;
|
|
11574
|
+
if (!activeBatch || !activeBatch.startsWith(trashRoot())) {
|
|
11575
|
+
activeBatch = batchDir(now);
|
|
11576
|
+
pruneTrash(now);
|
|
11577
|
+
}
|
|
11578
|
+
const parent = basename2(dirname6(sourcePath));
|
|
11579
|
+
const grandparent = basename2(dirname6(dirname6(sourcePath)));
|
|
11580
|
+
const destDir = join21(activeBatch, `${grandparent}__${parent}`.replace(/[^a-zA-Z0-9._-]/g, "-"));
|
|
11581
|
+
const dest = join21(destDir, basename2(sourcePath));
|
|
11582
|
+
try {
|
|
11583
|
+
mkdirSync13(destDir, { recursive: true });
|
|
11584
|
+
try {
|
|
11585
|
+
renameSync3(sourcePath, dest);
|
|
11586
|
+
} catch {
|
|
11587
|
+
cpSync(sourcePath, dest, { recursive: true });
|
|
11588
|
+
rmSync4(sourcePath, { recursive: true, force: true });
|
|
11589
|
+
}
|
|
11590
|
+
} catch {
|
|
11591
|
+
return null;
|
|
11592
|
+
}
|
|
11593
|
+
const manifestPath = join21(activeBatch, "manifest.json");
|
|
11594
|
+
const manifest = readJsonOrNull(manifestPath) ?? { entries: [] };
|
|
11595
|
+
manifest.entries.push({ from: sourcePath, to: dest, reason, at: now.toISOString() });
|
|
11596
|
+
try {
|
|
11597
|
+
writeJsonAtomic(manifestPath, manifest);
|
|
11598
|
+
} catch {}
|
|
11599
|
+
return dest;
|
|
11600
|
+
}
|
|
11601
|
+
function currentTrashBatch() {
|
|
11602
|
+
return activeBatch;
|
|
11603
|
+
}
|
|
11604
|
+
|
|
11507
11605
|
// src/agents/utils/json-config.ts
|
|
11508
|
-
import { readFileSync as
|
|
11509
|
-
import { dirname as
|
|
11606
|
+
import { readFileSync as readFileSync23, writeFileSync as writeFileSync14, mkdirSync as mkdirSync14, existsSync as existsSync27 } from "fs";
|
|
11607
|
+
import { dirname as dirname7 } from "path";
|
|
11510
11608
|
|
|
11511
11609
|
// src/sync/hash.ts
|
|
11512
11610
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -11538,21 +11636,21 @@ function isRunworkManagedKey(key) {
|
|
|
11538
11636
|
return key === RUNWORK_WORKSPACE_MCP_NAME || key.startsWith(RUNWORK_MCP_PREFIX) || key.startsWith(RUNWORK_MCP_PREFIX_LEGACY);
|
|
11539
11637
|
}
|
|
11540
11638
|
function readJsonConfig(filePath) {
|
|
11541
|
-
if (!
|
|
11639
|
+
if (!existsSync27(filePath))
|
|
11542
11640
|
return {};
|
|
11543
11641
|
try {
|
|
11544
|
-
return JSON.parse(
|
|
11642
|
+
return JSON.parse(readFileSync23(filePath, "utf-8"));
|
|
11545
11643
|
} catch {
|
|
11546
11644
|
return {};
|
|
11547
11645
|
}
|
|
11548
11646
|
}
|
|
11549
11647
|
function writeJsonConfig(filePath, config) {
|
|
11550
|
-
|
|
11648
|
+
mkdirSync14(dirname7(filePath), { recursive: true });
|
|
11551
11649
|
writeFileSync14(filePath, JSON.stringify(config, null, 2) + `
|
|
11552
11650
|
`);
|
|
11553
11651
|
}
|
|
11554
11652
|
function removeRunworkMcpServers(filePath, topKey) {
|
|
11555
|
-
if (!
|
|
11653
|
+
if (!existsSync27(filePath))
|
|
11556
11654
|
return false;
|
|
11557
11655
|
const config = readJsonConfig(filePath);
|
|
11558
11656
|
const existing = config[topKey] || {};
|
|
@@ -11591,18 +11689,46 @@ function mergeJsonMcpServers(filePath, servers, topKey) {
|
|
|
11591
11689
|
return true;
|
|
11592
11690
|
}
|
|
11593
11691
|
|
|
11692
|
+
// src/agents/utils/skill-removal.ts
|
|
11693
|
+
import { existsSync as existsSync28, readdirSync as readdirSync6 } from "fs";
|
|
11694
|
+
import { join as join22 } from "path";
|
|
11695
|
+
function removeMatchingSkillDirs(dir, allowed, reason = "skill removed") {
|
|
11696
|
+
if (!allowed.size || !existsSync28(dir))
|
|
11697
|
+
return;
|
|
11698
|
+
for (const entry of readdirSync6(dir)) {
|
|
11699
|
+
if (!allowed.has(entry))
|
|
11700
|
+
continue;
|
|
11701
|
+
moveToTrash(join22(dir, entry), reason);
|
|
11702
|
+
}
|
|
11703
|
+
}
|
|
11704
|
+
function removeMatchingSkillFiles(dir, allowed, suffix, reason = "skill removed") {
|
|
11705
|
+
if (!allowed.size || !existsSync28(dir))
|
|
11706
|
+
return;
|
|
11707
|
+
const names = new Set([...allowed].map((slug) => `${slug}${suffix}`));
|
|
11708
|
+
for (const entry of readdirSync6(dir)) {
|
|
11709
|
+
if (!names.has(entry))
|
|
11710
|
+
continue;
|
|
11711
|
+
moveToTrash(join22(dir, entry), reason);
|
|
11712
|
+
}
|
|
11713
|
+
}
|
|
11714
|
+
|
|
11594
11715
|
// src/agents/utils/instruction-hint.ts
|
|
11595
|
-
import { existsSync as
|
|
11596
|
-
import { dirname as
|
|
11716
|
+
import { existsSync as existsSync29, readFileSync as readFileSync24, writeFileSync as writeFileSync15, mkdirSync as mkdirSync15 } from "fs";
|
|
11717
|
+
import { dirname as dirname8 } from "path";
|
|
11597
11718
|
var START_MARKER = "<!-- runwork:start -->";
|
|
11598
11719
|
var END_MARKER = "<!-- runwork:end -->";
|
|
11599
11720
|
var TEAM_START_MARKER = "<!-- runwork-team:start -->";
|
|
11600
11721
|
var TEAM_END_MARKER = "<!-- runwork-team:end -->";
|
|
11601
11722
|
function writeHintToFile(filePath, hint) {
|
|
11602
|
-
|
|
11723
|
+
mkdirSync15(dirname8(filePath), { recursive: true });
|
|
11724
|
+
if (!hint.includes(START_MARKER) || !hint.includes(END_MARKER)) {
|
|
11725
|
+
hint = `${START_MARKER}
|
|
11726
|
+
${hint}
|
|
11727
|
+
${END_MARKER}`;
|
|
11728
|
+
}
|
|
11603
11729
|
let content = "";
|
|
11604
|
-
if (
|
|
11605
|
-
content =
|
|
11730
|
+
if (existsSync29(filePath)) {
|
|
11731
|
+
content = readFileSync24(filePath, "utf-8");
|
|
11606
11732
|
}
|
|
11607
11733
|
const startIdx = content.indexOf(START_MARKER);
|
|
11608
11734
|
const endIdx = content.indexOf(END_MARKER);
|
|
@@ -11624,9 +11750,9 @@ function writeHintToFile(filePath, hint) {
|
|
|
11624
11750
|
writeFileSync15(filePath, content);
|
|
11625
11751
|
}
|
|
11626
11752
|
function removeHintFromFile(filePath) {
|
|
11627
|
-
if (!
|
|
11753
|
+
if (!existsSync29(filePath))
|
|
11628
11754
|
return false;
|
|
11629
|
-
let content =
|
|
11755
|
+
let content = readFileSync24(filePath, "utf-8");
|
|
11630
11756
|
const startIdx = content.indexOf(START_MARKER);
|
|
11631
11757
|
const endIdx = content.indexOf(END_MARKER);
|
|
11632
11758
|
if (startIdx < 0 || endIdx < 0)
|
|
@@ -11644,9 +11770,9 @@ function removeHintFromFile(filePath) {
|
|
|
11644
11770
|
return true;
|
|
11645
11771
|
}
|
|
11646
11772
|
function removeTeamInstructionsFromFile(filePath) {
|
|
11647
|
-
if (!
|
|
11773
|
+
if (!existsSync29(filePath))
|
|
11648
11774
|
return false;
|
|
11649
|
-
let content =
|
|
11775
|
+
let content = readFileSync24(filePath, "utf-8");
|
|
11650
11776
|
const startIdx = content.indexOf(TEAM_START_MARKER);
|
|
11651
11777
|
const endIdx = content.indexOf(TEAM_END_MARKER);
|
|
11652
11778
|
if (startIdx < 0 || endIdx < 0)
|
|
@@ -11664,10 +11790,10 @@ function removeTeamInstructionsFromFile(filePath) {
|
|
|
11664
11790
|
return true;
|
|
11665
11791
|
}
|
|
11666
11792
|
function writeTeamInstructionsToFile(filePath, instructions) {
|
|
11667
|
-
|
|
11793
|
+
mkdirSync15(dirname8(filePath), { recursive: true });
|
|
11668
11794
|
let content = "";
|
|
11669
|
-
if (
|
|
11670
|
-
content =
|
|
11795
|
+
if (existsSync29(filePath)) {
|
|
11796
|
+
content = readFileSync24(filePath, "utf-8");
|
|
11671
11797
|
}
|
|
11672
11798
|
const block = `${TEAM_START_MARKER}
|
|
11673
11799
|
${instructions}
|
|
@@ -11974,7 +12100,7 @@ class ClaudeCodeAdapter {
|
|
|
11974
12100
|
if (whichBinary("claude"))
|
|
11975
12101
|
return true;
|
|
11976
12102
|
if (platform2() === "darwin")
|
|
11977
|
-
return
|
|
12103
|
+
return existsSync30("/Applications/Claude.app");
|
|
11978
12104
|
return false;
|
|
11979
12105
|
}
|
|
11980
12106
|
supportsMcpScope(_scope) {
|
|
@@ -11985,7 +12111,7 @@ class ClaudeCodeAdapter {
|
|
|
11985
12111
|
}
|
|
11986
12112
|
async writeMcpServers(servers, scope) {
|
|
11987
12113
|
if (scope === "project") {
|
|
11988
|
-
const filePath =
|
|
12114
|
+
const filePath = join23(process.cwd(), ".mcp.json");
|
|
11989
12115
|
const entries = {};
|
|
11990
12116
|
for (const s of servers) {
|
|
11991
12117
|
entries[s.name] = {
|
|
@@ -11997,7 +12123,7 @@ class ClaudeCodeAdapter {
|
|
|
11997
12123
|
}
|
|
11998
12124
|
mergeJsonMcpServers(filePath, entries, "mcpServers");
|
|
11999
12125
|
} else {
|
|
12000
|
-
const settingsPath =
|
|
12126
|
+
const settingsPath = join23(homedir6(), ".claude", "settings.json");
|
|
12001
12127
|
const entries = {};
|
|
12002
12128
|
for (const s of servers) {
|
|
12003
12129
|
entries[s.name] = {
|
|
@@ -12009,11 +12135,11 @@ class ClaudeCodeAdapter {
|
|
|
12009
12135
|
}
|
|
12010
12136
|
mergeJsonMcpServers(settingsPath, entries, "mcpServers");
|
|
12011
12137
|
const pluginDir = this.getPluginDir();
|
|
12012
|
-
|
|
12013
|
-
const pluginMcpPath =
|
|
12138
|
+
mkdirSync16(pluginDir, { recursive: true });
|
|
12139
|
+
const pluginMcpPath = join23(pluginDir, ".mcp.json");
|
|
12014
12140
|
const marketDir = this.getMarketplaceDir();
|
|
12015
|
-
|
|
12016
|
-
const marketplaceMcpPath =
|
|
12141
|
+
mkdirSync16(marketDir, { recursive: true });
|
|
12142
|
+
const marketplaceMcpPath = join23(marketDir, ".mcp.json");
|
|
12017
12143
|
const pluginEntries = {};
|
|
12018
12144
|
for (const s of servers) {
|
|
12019
12145
|
pluginEntries[s.name] = {
|
|
@@ -12028,44 +12154,44 @@ class ClaudeCodeAdapter {
|
|
|
12028
12154
|
}
|
|
12029
12155
|
}
|
|
12030
12156
|
installSessionStartHook(pluginDir, label) {
|
|
12031
|
-
const hooksDir =
|
|
12032
|
-
|
|
12033
|
-
const scriptPath =
|
|
12157
|
+
const hooksDir = join23(pluginDir, "hooks");
|
|
12158
|
+
mkdirSync16(hooksDir, { recursive: true });
|
|
12159
|
+
const scriptPath = join23(hooksDir, "on-session-start.sh");
|
|
12034
12160
|
writeFileSync16(scriptPath, SESSION_START_HOOK_SCRIPT);
|
|
12035
12161
|
try {
|
|
12036
12162
|
chmodSync(scriptPath, 493);
|
|
12037
12163
|
} catch {}
|
|
12038
|
-
writeFileSync16(
|
|
12164
|
+
writeFileSync16(join23(hooksDir, "hooks.json"), JSON.stringify(SESSION_START_HOOKS_MANIFEST, null, 2));
|
|
12039
12165
|
vlog(` [Claude Code] Installed SessionStart hook (${label}) -> ${scriptPath}`);
|
|
12040
12166
|
}
|
|
12041
12167
|
async writeSkills(skills, scope) {
|
|
12042
|
-
const baseDir = scope === "project" ?
|
|
12168
|
+
const baseDir = scope === "project" ? join23(process.cwd(), ".claude", "skills") : join23(homedir6(), ".claude", "skills");
|
|
12043
12169
|
for (const skill of skills) {
|
|
12044
|
-
const skillDir =
|
|
12170
|
+
const skillDir = join23(baseDir, skill.filename);
|
|
12045
12171
|
cleanupOldSkillDir(baseDir, skill);
|
|
12046
|
-
|
|
12047
|
-
writeFileSync16(
|
|
12172
|
+
mkdirSync16(skillDir, { recursive: true });
|
|
12173
|
+
writeFileSync16(join23(skillDir, "SKILL.md"), buildSkillMd2(skill));
|
|
12048
12174
|
}
|
|
12049
12175
|
if (scope === "user") {
|
|
12050
12176
|
const pluginDir = this.getPluginDir();
|
|
12051
|
-
const pluginJsonDir =
|
|
12052
|
-
|
|
12053
|
-
writeFileSync16(
|
|
12177
|
+
const pluginJsonDir = join23(pluginDir, ".claude-plugin");
|
|
12178
|
+
mkdirSync16(pluginJsonDir, { recursive: true });
|
|
12179
|
+
writeFileSync16(join23(pluginJsonDir, "plugin.json"), JSON.stringify(getPluginJson(), null, 2));
|
|
12054
12180
|
for (const skill of skills) {
|
|
12055
|
-
const skillDir =
|
|
12056
|
-
cleanupOldSkillDir(
|
|
12057
|
-
|
|
12058
|
-
writeFileSync16(
|
|
12181
|
+
const skillDir = join23(pluginDir, "skills", skill.filename);
|
|
12182
|
+
cleanupOldSkillDir(join23(pluginDir, "skills"), skill);
|
|
12183
|
+
mkdirSync16(skillDir, { recursive: true });
|
|
12184
|
+
writeFileSync16(join23(skillDir, "SKILL.md"), buildSkillMd2(skill));
|
|
12059
12185
|
}
|
|
12060
12186
|
const marketDir = this.getMarketplaceDir();
|
|
12061
|
-
const marketPluginJsonDir =
|
|
12062
|
-
|
|
12063
|
-
writeFileSync16(
|
|
12187
|
+
const marketPluginJsonDir = join23(marketDir, ".claude-plugin");
|
|
12188
|
+
mkdirSync16(marketPluginJsonDir, { recursive: true });
|
|
12189
|
+
writeFileSync16(join23(marketPluginJsonDir, "plugin.json"), JSON.stringify(getPluginJson(), null, 2));
|
|
12064
12190
|
for (const skill of skills) {
|
|
12065
|
-
const skillDir =
|
|
12066
|
-
cleanupOldSkillDir(
|
|
12067
|
-
|
|
12068
|
-
writeFileSync16(
|
|
12191
|
+
const skillDir = join23(marketDir, "skills", skill.filename);
|
|
12192
|
+
cleanupOldSkillDir(join23(marketDir, "skills"), skill);
|
|
12193
|
+
mkdirSync16(skillDir, { recursive: true });
|
|
12194
|
+
writeFileSync16(join23(skillDir, "SKILL.md"), buildSkillMd2(skill));
|
|
12069
12195
|
}
|
|
12070
12196
|
this.registerPlugin(pluginDir);
|
|
12071
12197
|
}
|
|
@@ -12076,19 +12202,19 @@ class ClaudeCodeAdapter {
|
|
|
12076
12202
|
return;
|
|
12077
12203
|
const pluginDir = this.getPluginDir();
|
|
12078
12204
|
const marketDir = this.getMarketplaceDir();
|
|
12079
|
-
if (
|
|
12205
|
+
if (existsSync30(pluginDir)) {
|
|
12080
12206
|
this.installSessionStartHook(pluginDir, "plugin cache");
|
|
12081
12207
|
}
|
|
12082
|
-
if (
|
|
12208
|
+
if (existsSync30(marketDir)) {
|
|
12083
12209
|
this.installSessionStartHook(marketDir, "marketplace");
|
|
12084
12210
|
}
|
|
12085
12211
|
}
|
|
12086
12212
|
async writeInstructionHint(hint, scope) {
|
|
12087
|
-
const filePath = scope === "project" ?
|
|
12213
|
+
const filePath = scope === "project" ? join23(process.cwd(), ".claude", "CLAUDE.md") : join23(homedir6(), ".claude", "CLAUDE.md");
|
|
12088
12214
|
writeHintToFile(filePath, hint);
|
|
12089
12215
|
}
|
|
12090
12216
|
async writeTeamInstructions(instructions, scope) {
|
|
12091
|
-
const filePath = scope === "project" ?
|
|
12217
|
+
const filePath = scope === "project" ? join23(process.cwd(), ".claude", "CLAUDE.md") : join23(homedir6(), ".claude", "CLAUDE.md");
|
|
12092
12218
|
writeTeamInstructionsToFile(filePath, instructions);
|
|
12093
12219
|
if (scope === "user") {
|
|
12094
12220
|
const skillContent = `---
|
|
@@ -12100,21 +12226,21 @@ ${instructions}`;
|
|
|
12100
12226
|
const pluginDir = this.getPluginDir();
|
|
12101
12227
|
const marketDir = this.getMarketplaceDir();
|
|
12102
12228
|
for (const dir of [
|
|
12103
|
-
|
|
12104
|
-
|
|
12229
|
+
join23(pluginDir, "skills", "runwork-team-instructions"),
|
|
12230
|
+
join23(marketDir, "skills", "runwork-team-instructions")
|
|
12105
12231
|
]) {
|
|
12106
|
-
|
|
12107
|
-
writeFileSync16(
|
|
12232
|
+
mkdirSync16(dir, { recursive: true });
|
|
12233
|
+
writeFileSync16(join23(dir, "SKILL.md"), skillContent);
|
|
12108
12234
|
}
|
|
12109
12235
|
}
|
|
12110
12236
|
}
|
|
12111
12237
|
async writeAgentConfig(config, scope, baseline) {
|
|
12112
|
-
const settingsPath = scope === "project" ?
|
|
12113
|
-
const hadFile =
|
|
12238
|
+
const settingsPath = scope === "project" ? join23(process.cwd(), ".claude", "settings.json") : join23(homedir6(), ".claude", "settings.json");
|
|
12239
|
+
const hadFile = existsSync30(settingsPath);
|
|
12114
12240
|
let settings = {};
|
|
12115
12241
|
if (hadFile) {
|
|
12116
12242
|
try {
|
|
12117
|
-
settings = JSON.parse(
|
|
12243
|
+
settings = JSON.parse(readFileSync25(settingsPath, "utf-8"));
|
|
12118
12244
|
} catch {}
|
|
12119
12245
|
}
|
|
12120
12246
|
if (settings.permissions && typeof settings.permissions === "object") {
|
|
@@ -12144,37 +12270,39 @@ ${instructions}`;
|
|
|
12144
12270
|
}
|
|
12145
12271
|
if (!hadFile && !config.modelPreference && !config.permissionRules)
|
|
12146
12272
|
return;
|
|
12147
|
-
|
|
12273
|
+
mkdirSync16(join23(settingsPath, ".."), { recursive: true });
|
|
12148
12274
|
writeFileSync16(settingsPath, JSON.stringify(settings, null, 2));
|
|
12149
12275
|
}
|
|
12150
12276
|
async readManagedBlock(_scope) {
|
|
12151
12277
|
return;
|
|
12152
12278
|
}
|
|
12279
|
+
async removeSkills(skillFilenames, scope) {
|
|
12280
|
+
if (!skillFilenames.length)
|
|
12281
|
+
return;
|
|
12282
|
+
const allowed = new Set(skillFilenames);
|
|
12283
|
+
const roots = scope === "project" ? [join23(process.cwd(), ".claude", "skills")] : [
|
|
12284
|
+
join23(homedir6(), ".claude", "skills"),
|
|
12285
|
+
join23(this.getPluginDir(), "skills"),
|
|
12286
|
+
join23(this.getMarketplaceDir(), "skills")
|
|
12287
|
+
];
|
|
12288
|
+
for (const dir of roots)
|
|
12289
|
+
removeMatchingSkillDirs(dir, allowed);
|
|
12290
|
+
}
|
|
12153
12291
|
async cleanup(scope, manifest) {
|
|
12154
12292
|
if (scope === "project") {
|
|
12155
|
-
removeRunworkMcpServers(
|
|
12293
|
+
removeRunworkMcpServers(join23(process.cwd(), ".mcp.json"), "mcpServers");
|
|
12156
12294
|
} else {
|
|
12157
|
-
removeRunworkMcpServers(
|
|
12295
|
+
removeRunworkMcpServers(join23(homedir6(), ".claude", "settings.json"), "mcpServers");
|
|
12158
12296
|
}
|
|
12159
|
-
|
|
12160
|
-
|
|
12161
|
-
const allowed = new Set(manifest.skillFilenames);
|
|
12162
|
-
for (const entry of readdirSync5(skillsDir)) {
|
|
12163
|
-
if (!allowed.has(entry))
|
|
12164
|
-
continue;
|
|
12165
|
-
try {
|
|
12166
|
-
rmSync4(join21(skillsDir, entry), { recursive: true, force: true });
|
|
12167
|
-
} catch {}
|
|
12168
|
-
}
|
|
12169
|
-
}
|
|
12170
|
-
const instructionFile = scope === "project" ? join21(process.cwd(), ".claude", "CLAUDE.md") : join21(homedir5(), ".claude", "CLAUDE.md");
|
|
12297
|
+
await this.removeSkills(manifest?.skillFilenames ?? [], scope);
|
|
12298
|
+
const instructionFile = scope === "project" ? join23(process.cwd(), ".claude", "CLAUDE.md") : join23(homedir6(), ".claude", "CLAUDE.md");
|
|
12171
12299
|
removeHintFromFile(instructionFile);
|
|
12172
12300
|
removeTeamInstructionsFromFile(instructionFile);
|
|
12173
12301
|
if (scope === "user") {
|
|
12174
|
-
const settingsPath =
|
|
12175
|
-
if (
|
|
12302
|
+
const settingsPath = join23(homedir6(), ".claude", "settings.json");
|
|
12303
|
+
if (existsSync30(settingsPath)) {
|
|
12176
12304
|
try {
|
|
12177
|
-
const settings = JSON.parse(
|
|
12305
|
+
const settings = JSON.parse(readFileSync25(settingsPath, "utf-8"));
|
|
12178
12306
|
if (settings.permissions) {
|
|
12179
12307
|
for (const key of ["allow", "deny"]) {
|
|
12180
12308
|
const arr = settings.permissions[key];
|
|
@@ -12185,7 +12313,10 @@ ${instructions}`;
|
|
|
12185
12313
|
}
|
|
12186
12314
|
}
|
|
12187
12315
|
if (settings.enabledPlugins) {
|
|
12188
|
-
|
|
12316
|
+
for (const key of Object.keys(settings.enabledPlugins)) {
|
|
12317
|
+
if (isRunworkPluginKey(key))
|
|
12318
|
+
delete settings.enabledPlugins[key];
|
|
12319
|
+
}
|
|
12189
12320
|
}
|
|
12190
12321
|
writeFileSync16(settingsPath, JSON.stringify(settings, null, 2));
|
|
12191
12322
|
} catch {}
|
|
@@ -12193,25 +12324,28 @@ ${instructions}`;
|
|
|
12193
12324
|
const pluginDir = this.getPluginDir();
|
|
12194
12325
|
const marketRoot = this.getMarketplaceRoot();
|
|
12195
12326
|
for (const dir of [pluginDir, marketRoot]) {
|
|
12196
|
-
if (
|
|
12327
|
+
if (existsSync30(dir)) {
|
|
12197
12328
|
try {
|
|
12198
|
-
|
|
12329
|
+
rmSync5(dir, { recursive: true, force: true });
|
|
12199
12330
|
} catch {}
|
|
12200
12331
|
}
|
|
12201
12332
|
}
|
|
12202
12333
|
const pluginsBase = this.getPluginsBaseDir();
|
|
12203
|
-
const installedPath =
|
|
12204
|
-
if (
|
|
12334
|
+
const installedPath = join23(pluginsBase, "installed_plugins.json");
|
|
12335
|
+
if (existsSync30(installedPath)) {
|
|
12205
12336
|
try {
|
|
12206
12337
|
const installed = readJsonConfig(installedPath);
|
|
12207
12338
|
if (installed.plugins) {
|
|
12208
|
-
|
|
12339
|
+
for (const key of Object.keys(installed.plugins)) {
|
|
12340
|
+
if (isRunworkPluginKey(key))
|
|
12341
|
+
delete installed.plugins[key];
|
|
12342
|
+
}
|
|
12209
12343
|
writeJsonConfig(installedPath, installed);
|
|
12210
12344
|
}
|
|
12211
12345
|
} catch {}
|
|
12212
12346
|
}
|
|
12213
|
-
const marketplacesPath =
|
|
12214
|
-
if (
|
|
12347
|
+
const marketplacesPath = join23(pluginsBase, "known_marketplaces.json");
|
|
12348
|
+
if (existsSync30(marketplacesPath)) {
|
|
12215
12349
|
try {
|
|
12216
12350
|
const marketplaces = readJsonConfig(marketplacesPath);
|
|
12217
12351
|
delete marketplaces["runwork"];
|
|
@@ -12222,11 +12356,11 @@ ${instructions}`;
|
|
|
12222
12356
|
}
|
|
12223
12357
|
async readUsageStats(lastSyncAt) {
|
|
12224
12358
|
try {
|
|
12225
|
-
const claudeDir =
|
|
12226
|
-
if (!
|
|
12359
|
+
const claudeDir = join23(homedir6(), ".claude");
|
|
12360
|
+
if (!existsSync30(claudeDir))
|
|
12227
12361
|
return null;
|
|
12228
|
-
const projectsDir =
|
|
12229
|
-
if (!
|
|
12362
|
+
const projectsDir = join23(claudeDir, "projects");
|
|
12363
|
+
if (!existsSync30(projectsDir))
|
|
12230
12364
|
return null;
|
|
12231
12365
|
const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
|
|
12232
12366
|
let sessionCount = 0;
|
|
@@ -12240,25 +12374,25 @@ ${instructions}`;
|
|
|
12240
12374
|
const seenEntries = new Set;
|
|
12241
12375
|
let cwdEntries;
|
|
12242
12376
|
try {
|
|
12243
|
-
cwdEntries =
|
|
12377
|
+
cwdEntries = readdirSync7(projectsDir);
|
|
12244
12378
|
} catch {
|
|
12245
12379
|
return null;
|
|
12246
12380
|
}
|
|
12247
12381
|
for (const cwd of cwdEntries) {
|
|
12248
|
-
const cwdPath =
|
|
12382
|
+
const cwdPath = join23(projectsDir, cwd);
|
|
12249
12383
|
let files;
|
|
12250
12384
|
try {
|
|
12251
|
-
files =
|
|
12385
|
+
files = readdirSync7(cwdPath);
|
|
12252
12386
|
} catch {
|
|
12253
12387
|
continue;
|
|
12254
12388
|
}
|
|
12255
12389
|
for (const file of files) {
|
|
12256
12390
|
if (!file.endsWith(".jsonl"))
|
|
12257
12391
|
continue;
|
|
12258
|
-
const filePath =
|
|
12392
|
+
const filePath = join23(cwdPath, file);
|
|
12259
12393
|
let stat;
|
|
12260
12394
|
try {
|
|
12261
|
-
stat =
|
|
12395
|
+
stat = statSync4(filePath);
|
|
12262
12396
|
} catch {
|
|
12263
12397
|
continue;
|
|
12264
12398
|
}
|
|
@@ -12266,7 +12400,7 @@ ${instructions}`;
|
|
|
12266
12400
|
continue;
|
|
12267
12401
|
let content;
|
|
12268
12402
|
try {
|
|
12269
|
-
content =
|
|
12403
|
+
content = readFileSync25(filePath, "utf-8");
|
|
12270
12404
|
} catch {
|
|
12271
12405
|
continue;
|
|
12272
12406
|
}
|
|
@@ -12352,32 +12486,32 @@ ${instructions}`;
|
|
|
12352
12486
|
}
|
|
12353
12487
|
async readSessionDigests(sinceISO) {
|
|
12354
12488
|
try {
|
|
12355
|
-
const projectsDir =
|
|
12356
|
-
if (!
|
|
12489
|
+
const projectsDir = join23(homedir6(), ".claude", "projects");
|
|
12490
|
+
if (!existsSync30(projectsDir))
|
|
12357
12491
|
return null;
|
|
12358
12492
|
const sinceMs = sinceISO ? new Date(sinceISO).getTime() : 0;
|
|
12359
12493
|
let cwdEntries;
|
|
12360
12494
|
try {
|
|
12361
|
-
cwdEntries =
|
|
12495
|
+
cwdEntries = readdirSync7(projectsDir);
|
|
12362
12496
|
} catch {
|
|
12363
12497
|
return null;
|
|
12364
12498
|
}
|
|
12365
12499
|
const digests = [];
|
|
12366
12500
|
for (const cwd of cwdEntries) {
|
|
12367
|
-
const cwdPath =
|
|
12501
|
+
const cwdPath = join23(projectsDir, cwd);
|
|
12368
12502
|
let files;
|
|
12369
12503
|
try {
|
|
12370
|
-
files =
|
|
12504
|
+
files = readdirSync7(cwdPath);
|
|
12371
12505
|
} catch {
|
|
12372
12506
|
continue;
|
|
12373
12507
|
}
|
|
12374
12508
|
for (const file of files) {
|
|
12375
12509
|
if (!file.endsWith(".jsonl"))
|
|
12376
12510
|
continue;
|
|
12377
|
-
const filePath =
|
|
12511
|
+
const filePath = join23(cwdPath, file);
|
|
12378
12512
|
let stat;
|
|
12379
12513
|
try {
|
|
12380
|
-
stat =
|
|
12514
|
+
stat = statSync4(filePath);
|
|
12381
12515
|
} catch {
|
|
12382
12516
|
continue;
|
|
12383
12517
|
}
|
|
@@ -12385,7 +12519,7 @@ ${instructions}`;
|
|
|
12385
12519
|
continue;
|
|
12386
12520
|
let content;
|
|
12387
12521
|
try {
|
|
12388
|
-
content =
|
|
12522
|
+
content = readFileSync25(filePath, "utf-8");
|
|
12389
12523
|
} catch {
|
|
12390
12524
|
continue;
|
|
12391
12525
|
}
|
|
@@ -12401,8 +12535,8 @@ ${instructions}`;
|
|
|
12401
12535
|
}
|
|
12402
12536
|
async readSkillUsage(lastSyncAt) {
|
|
12403
12537
|
try {
|
|
12404
|
-
const projectsDir =
|
|
12405
|
-
if (!
|
|
12538
|
+
const projectsDir = join23(homedir6(), ".claude", "projects");
|
|
12539
|
+
if (!existsSync30(projectsDir))
|
|
12406
12540
|
return null;
|
|
12407
12541
|
const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
|
|
12408
12542
|
const skillCounts = new Map;
|
|
@@ -12418,25 +12552,25 @@ ${instructions}`;
|
|
|
12418
12552
|
};
|
|
12419
12553
|
let cwdEntries;
|
|
12420
12554
|
try {
|
|
12421
|
-
cwdEntries =
|
|
12555
|
+
cwdEntries = readdirSync7(projectsDir);
|
|
12422
12556
|
} catch {
|
|
12423
12557
|
return null;
|
|
12424
12558
|
}
|
|
12425
12559
|
for (const cwd of cwdEntries) {
|
|
12426
|
-
const cwdPath =
|
|
12560
|
+
const cwdPath = join23(projectsDir, cwd);
|
|
12427
12561
|
let files;
|
|
12428
12562
|
try {
|
|
12429
|
-
files =
|
|
12563
|
+
files = readdirSync7(cwdPath);
|
|
12430
12564
|
} catch {
|
|
12431
12565
|
continue;
|
|
12432
12566
|
}
|
|
12433
12567
|
for (const file of files) {
|
|
12434
12568
|
if (!file.endsWith(".jsonl"))
|
|
12435
12569
|
continue;
|
|
12436
|
-
const filePath =
|
|
12570
|
+
const filePath = join23(cwdPath, file);
|
|
12437
12571
|
let fileStat;
|
|
12438
12572
|
try {
|
|
12439
|
-
fileStat =
|
|
12573
|
+
fileStat = statSync4(filePath);
|
|
12440
12574
|
} catch {
|
|
12441
12575
|
continue;
|
|
12442
12576
|
}
|
|
@@ -12444,7 +12578,7 @@ ${instructions}`;
|
|
|
12444
12578
|
continue;
|
|
12445
12579
|
let content;
|
|
12446
12580
|
try {
|
|
12447
|
-
content =
|
|
12581
|
+
content = readFileSync25(filePath, "utf-8");
|
|
12448
12582
|
} catch {
|
|
12449
12583
|
continue;
|
|
12450
12584
|
}
|
|
@@ -12534,21 +12668,21 @@ ${instructions}`;
|
|
|
12534
12668
|
}
|
|
12535
12669
|
}
|
|
12536
12670
|
getPluginsBaseDir() {
|
|
12537
|
-
return
|
|
12671
|
+
return join23(homedir6(), ".claude", "plugins");
|
|
12538
12672
|
}
|
|
12539
12673
|
getPluginDir() {
|
|
12540
|
-
return
|
|
12674
|
+
return join23(this.getPluginsBaseDir(), "cache", "runwork", PLUGIN_NAME, PLUGIN_VERSION);
|
|
12541
12675
|
}
|
|
12542
12676
|
getMarketplaceRoot() {
|
|
12543
|
-
return
|
|
12677
|
+
return join23(this.getPluginsBaseDir(), "marketplaces", "runwork");
|
|
12544
12678
|
}
|
|
12545
12679
|
getMarketplaceDir() {
|
|
12546
|
-
return
|
|
12680
|
+
return join23(this.getMarketplaceRoot(), "plugins", PLUGIN_NAME);
|
|
12547
12681
|
}
|
|
12548
12682
|
registerPlugin(pluginDir) {
|
|
12549
12683
|
const pluginsBase = this.getPluginsBaseDir();
|
|
12550
|
-
|
|
12551
|
-
const installedPath =
|
|
12684
|
+
mkdirSync16(pluginsBase, { recursive: true });
|
|
12685
|
+
const installedPath = join23(pluginsBase, "installed_plugins.json");
|
|
12552
12686
|
const installed = readJsonConfig(installedPath);
|
|
12553
12687
|
if (!installed.version)
|
|
12554
12688
|
installed.version = 2;
|
|
@@ -12565,10 +12699,10 @@ ${instructions}`;
|
|
|
12565
12699
|
}];
|
|
12566
12700
|
writeJsonConfig(installedPath, installed);
|
|
12567
12701
|
const marketRoot = this.getMarketplaceRoot();
|
|
12568
|
-
const marketCatalogDir =
|
|
12569
|
-
|
|
12570
|
-
writeFileSync16(
|
|
12571
|
-
const marketplacesPath =
|
|
12702
|
+
const marketCatalogDir = join23(marketRoot, ".claude-plugin");
|
|
12703
|
+
mkdirSync16(marketCatalogDir, { recursive: true });
|
|
12704
|
+
writeFileSync16(join23(marketCatalogDir, "marketplace.json"), JSON.stringify(getMarketplaceJson(), null, 2));
|
|
12705
|
+
const marketplacesPath = join23(pluginsBase, "known_marketplaces.json");
|
|
12572
12706
|
const marketplaces = readJsonConfig(marketplacesPath);
|
|
12573
12707
|
marketplaces["runwork"] = {
|
|
12574
12708
|
source: { source: "directory", path: marketRoot },
|
|
@@ -12577,7 +12711,7 @@ ${instructions}`;
|
|
|
12577
12711
|
};
|
|
12578
12712
|
writeJsonConfig(marketplacesPath, marketplaces);
|
|
12579
12713
|
vlog(` [Claude Code] ${wasRegistered ? "Refreshed" : "Registered"} plugin ` + `${pluginKey} v${PLUGIN_VERSION} -> ${installedPath}`);
|
|
12580
|
-
const settingsPath =
|
|
12714
|
+
const settingsPath = join23(homedir6(), ".claude", "settings.json");
|
|
12581
12715
|
const settings = readJsonConfig(settingsPath);
|
|
12582
12716
|
if (!settings["enabledPlugins"]) {
|
|
12583
12717
|
settings["enabledPlugins"] = {};
|
|
@@ -12592,27 +12726,23 @@ ${instructions}`;
|
|
|
12592
12726
|
function cleanupOldSkillDir(baseDir, skill) {
|
|
12593
12727
|
if (skill.name === skill.filename)
|
|
12594
12728
|
return;
|
|
12595
|
-
const oldDir =
|
|
12596
|
-
|
|
12597
|
-
try {
|
|
12598
|
-
rmSync4(oldDir, { recursive: true, force: true });
|
|
12599
|
-
} catch {}
|
|
12600
|
-
}
|
|
12729
|
+
const oldDir = join23(baseDir, skill.name);
|
|
12730
|
+
moveToTrash(oldDir, `skill renamed to ${skill.filename}`);
|
|
12601
12731
|
}
|
|
12602
12732
|
|
|
12603
12733
|
// src/agents/claude-desktop.ts
|
|
12604
|
-
import { existsSync as
|
|
12605
|
-
import { dirname as
|
|
12606
|
-
import { homedir as
|
|
12734
|
+
import { existsSync as existsSync32, mkdtempSync as mkdtempSync3, readdirSync as readdirSync8, readFileSync as readFileSync26, rmSync as rmSync7, statSync as statSync5, writeFileSync as writeFileSync18, mkdirSync as mkdirSync18 } from "fs";
|
|
12735
|
+
import { dirname as dirname9, join as join25 } from "path";
|
|
12736
|
+
import { homedir as homedir7, platform as platform3, tmpdir as tmpdir3 } from "os";
|
|
12607
12737
|
init_zip();
|
|
12608
12738
|
|
|
12609
12739
|
// src/agents/claude-desktop-plugin-tree.ts
|
|
12610
|
-
import { chmodSync as chmodSync2, mkdirSync as
|
|
12611
|
-
import { join as
|
|
12740
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync17, rmSync as rmSync6, existsSync as existsSync31, writeFileSync as writeFileSync17 } from "fs";
|
|
12741
|
+
import { join as join24 } from "path";
|
|
12612
12742
|
function writePluginMetadata(destDir, metadata) {
|
|
12613
|
-
const pluginJsonDir =
|
|
12614
|
-
|
|
12615
|
-
writeFileSync17(
|
|
12743
|
+
const pluginJsonDir = join24(destDir, ".claude-plugin");
|
|
12744
|
+
mkdirSync17(pluginJsonDir, { recursive: true });
|
|
12745
|
+
writeFileSync17(join24(pluginJsonDir, "plugin.json"), JSON.stringify({
|
|
12616
12746
|
name: metadata.pluginName,
|
|
12617
12747
|
version: metadata.pluginVersion,
|
|
12618
12748
|
description: metadata.description,
|
|
@@ -12620,7 +12750,7 @@ function writePluginMetadata(destDir, metadata) {
|
|
|
12620
12750
|
}, null, 2));
|
|
12621
12751
|
}
|
|
12622
12752
|
function writePluginMcpConfig(destDir, mcpServers) {
|
|
12623
|
-
|
|
12753
|
+
mkdirSync17(destDir, { recursive: true });
|
|
12624
12754
|
const mcpEntries = {};
|
|
12625
12755
|
for (const server of mcpServers) {
|
|
12626
12756
|
if (server.name === RUNWORK_WORKSPACE_MCP_NAME) {
|
|
@@ -12639,49 +12769,49 @@ function writePluginMcpConfig(destDir, mcpServers) {
|
|
|
12639
12769
|
};
|
|
12640
12770
|
}
|
|
12641
12771
|
}
|
|
12642
|
-
writeFileSync17(
|
|
12772
|
+
writeFileSync17(join24(destDir, ".mcp.json"), JSON.stringify({ mcpServers: mcpEntries }, null, 2));
|
|
12643
12773
|
}
|
|
12644
12774
|
function writePluginTeamInstructions(destDir, instructions) {
|
|
12645
|
-
const skillDir =
|
|
12646
|
-
|
|
12775
|
+
const skillDir = join24(destDir, "skills", "runwork-team-instructions");
|
|
12776
|
+
mkdirSync17(skillDir, { recursive: true });
|
|
12647
12777
|
const skillContent = `---
|
|
12648
12778
|
name: runwork-team-instructions
|
|
12649
12779
|
description: Team instructions from your Runwork workspace. Always follow these guidelines.
|
|
12650
12780
|
---
|
|
12651
12781
|
|
|
12652
12782
|
${instructions}`;
|
|
12653
|
-
writeFileSync17(
|
|
12783
|
+
writeFileSync17(join24(skillDir, "SKILL.md"), skillContent);
|
|
12654
12784
|
}
|
|
12655
12785
|
function writePluginSkills(destDir, skills) {
|
|
12656
|
-
const skillsRoot =
|
|
12657
|
-
|
|
12786
|
+
const skillsRoot = join24(destDir, "skills");
|
|
12787
|
+
mkdirSync17(skillsRoot, { recursive: true });
|
|
12658
12788
|
for (const skill of skills) {
|
|
12659
12789
|
if (skill.name !== skill.filename) {
|
|
12660
|
-
const legacyDir =
|
|
12661
|
-
if (
|
|
12790
|
+
const legacyDir = join24(skillsRoot, skill.name);
|
|
12791
|
+
if (existsSync31(legacyDir)) {
|
|
12662
12792
|
try {
|
|
12663
|
-
|
|
12793
|
+
rmSync6(legacyDir, { recursive: true, force: true });
|
|
12664
12794
|
} catch {}
|
|
12665
12795
|
}
|
|
12666
12796
|
}
|
|
12667
|
-
const skillDir =
|
|
12668
|
-
|
|
12669
|
-
writeFileSync17(
|
|
12797
|
+
const skillDir = join24(skillsRoot, skill.filename);
|
|
12798
|
+
mkdirSync17(skillDir, { recursive: true });
|
|
12799
|
+
writeFileSync17(join24(skillDir, "SKILL.md"), buildSkillMd2(skill));
|
|
12670
12800
|
}
|
|
12671
12801
|
}
|
|
12672
12802
|
function writePluginSessionStartHook(destDir) {
|
|
12673
|
-
const hooksDir =
|
|
12674
|
-
|
|
12675
|
-
const scriptPath =
|
|
12803
|
+
const hooksDir = join24(destDir, "hooks");
|
|
12804
|
+
mkdirSync17(hooksDir, { recursive: true });
|
|
12805
|
+
const scriptPath = join24(hooksDir, "on-session-start.sh");
|
|
12676
12806
|
writeFileSync17(scriptPath, SESSION_START_HOOK_SCRIPT);
|
|
12677
12807
|
try {
|
|
12678
12808
|
chmodSync2(scriptPath, 493);
|
|
12679
12809
|
} catch {}
|
|
12680
|
-
writeFileSync17(
|
|
12810
|
+
writeFileSync17(join24(hooksDir, "hooks.json"), JSON.stringify(SESSION_START_HOOKS_MANIFEST, null, 2));
|
|
12681
12811
|
console.log(` [Claude Desktop (Cowork)] Bundled SessionStart hook -> ${scriptPath}`);
|
|
12682
12812
|
}
|
|
12683
12813
|
function writePluginTree(destDir, input) {
|
|
12684
|
-
|
|
12814
|
+
mkdirSync17(destDir, { recursive: true });
|
|
12685
12815
|
writePluginMetadata(destDir, {
|
|
12686
12816
|
pluginName: input.pluginName,
|
|
12687
12817
|
pluginVersion: input.pluginVersion,
|
|
@@ -12698,6 +12828,9 @@ var PLUGIN_NAME2 = "runwork";
|
|
|
12698
12828
|
var PLUGIN_VERSION2 = "1.0.0";
|
|
12699
12829
|
var PLUGIN_DESCRIPTION = "Skills and tools from your Runwork workspace";
|
|
12700
12830
|
var PLUGIN_AUTHOR_NAME = "Runwork";
|
|
12831
|
+
function isRunworkRpmPluginName(name) {
|
|
12832
|
+
return !!name && (name === PLUGIN_NAME2 || name.startsWith(`${PLUGIN_NAME2}-`));
|
|
12833
|
+
}
|
|
12701
12834
|
function getPluginMetadata() {
|
|
12702
12835
|
return {
|
|
12703
12836
|
pluginName: PLUGIN_NAME2,
|
|
@@ -12709,39 +12842,59 @@ function getPluginMetadata() {
|
|
|
12709
12842
|
function getMcpConfigPath() {
|
|
12710
12843
|
const os2 = platform3();
|
|
12711
12844
|
if (os2 === "darwin") {
|
|
12712
|
-
return
|
|
12845
|
+
return join25(homedir7(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
12713
12846
|
}
|
|
12714
12847
|
if (os2 === "win32") {
|
|
12715
|
-
return
|
|
12848
|
+
return join25(process.env.APPDATA || join25(homedir7(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
|
12716
12849
|
}
|
|
12717
|
-
return
|
|
12850
|
+
return join25(homedir7(), ".config", "Claude", "claude_desktop_config.json");
|
|
12718
12851
|
}
|
|
12719
12852
|
function getCoworkBaseDir() {
|
|
12720
12853
|
const os2 = platform3();
|
|
12721
12854
|
if (os2 === "darwin") {
|
|
12722
|
-
return
|
|
12855
|
+
return join25(homedir7(), "Library", "Application Support", "Claude", "local-agent-mode-sessions");
|
|
12723
12856
|
}
|
|
12724
12857
|
if (os2 === "win32") {
|
|
12725
|
-
return
|
|
12858
|
+
return join25(process.env.APPDATA || join25(homedir7(), "AppData", "Roaming"), "Claude", "local-agent-mode-sessions");
|
|
12726
12859
|
}
|
|
12727
|
-
return
|
|
12860
|
+
return join25(homedir7(), ".config", "Claude", "local-agent-mode-sessions");
|
|
12861
|
+
}
|
|
12862
|
+
function listOrgDirs() {
|
|
12863
|
+
const baseDir = getCoworkBaseDir();
|
|
12864
|
+
if (!existsSync32(baseDir))
|
|
12865
|
+
return [];
|
|
12866
|
+
const dirs = [];
|
|
12867
|
+
try {
|
|
12868
|
+
const sessionDirs = readdirSync8(baseDir).filter((d) => !d.startsWith(".") && d !== "skills-plugin");
|
|
12869
|
+
for (const sessionId of sessionDirs) {
|
|
12870
|
+
const sessionPath = join25(baseDir, sessionId);
|
|
12871
|
+
try {
|
|
12872
|
+
for (const orgId of readdirSync8(sessionPath).filter((d) => !d.startsWith("."))) {
|
|
12873
|
+
dirs.push(join25(sessionPath, orgId));
|
|
12874
|
+
}
|
|
12875
|
+
} catch {
|
|
12876
|
+
continue;
|
|
12877
|
+
}
|
|
12878
|
+
}
|
|
12879
|
+
} catch {}
|
|
12880
|
+
return dirs;
|
|
12728
12881
|
}
|
|
12729
12882
|
function walkOrgDirs(visit) {
|
|
12730
12883
|
const baseDir = getCoworkBaseDir();
|
|
12731
|
-
if (!
|
|
12884
|
+
if (!existsSync32(baseDir))
|
|
12732
12885
|
return null;
|
|
12733
12886
|
try {
|
|
12734
|
-
const sessionDirs =
|
|
12887
|
+
const sessionDirs = readdirSync8(baseDir).filter((d) => !d.startsWith(".") && d !== "skills-plugin");
|
|
12735
12888
|
for (const sessionId of sessionDirs) {
|
|
12736
|
-
const sessionPath =
|
|
12889
|
+
const sessionPath = join25(baseDir, sessionId);
|
|
12737
12890
|
let orgDirs;
|
|
12738
12891
|
try {
|
|
12739
|
-
orgDirs =
|
|
12892
|
+
orgDirs = readdirSync8(sessionPath).filter((d) => !d.startsWith("."));
|
|
12740
12893
|
} catch {
|
|
12741
12894
|
continue;
|
|
12742
12895
|
}
|
|
12743
12896
|
for (const orgId of orgDirs) {
|
|
12744
|
-
const orgDir =
|
|
12897
|
+
const orgDir = join25(sessionPath, orgId);
|
|
12745
12898
|
const result = visit(orgDir);
|
|
12746
12899
|
if (result !== null)
|
|
12747
12900
|
return result;
|
|
@@ -12753,20 +12906,20 @@ function walkOrgDirs(visit) {
|
|
|
12753
12906
|
function getCoworkMemoryClaudeMdPaths() {
|
|
12754
12907
|
const paths = [];
|
|
12755
12908
|
const baseDir = getCoworkBaseDir();
|
|
12756
|
-
if (!
|
|
12909
|
+
if (!existsSync32(baseDir))
|
|
12757
12910
|
return paths;
|
|
12758
12911
|
try {
|
|
12759
|
-
const sessionDirs =
|
|
12912
|
+
const sessionDirs = readdirSync8(baseDir).filter((d) => !d.startsWith(".") && d !== "skills-plugin");
|
|
12760
12913
|
for (const sessionId of sessionDirs) {
|
|
12761
|
-
const sessionPath =
|
|
12914
|
+
const sessionPath = join25(baseDir, sessionId);
|
|
12762
12915
|
let orgDirs;
|
|
12763
12916
|
try {
|
|
12764
|
-
orgDirs =
|
|
12917
|
+
orgDirs = readdirSync8(sessionPath).filter((d) => !d.startsWith("."));
|
|
12765
12918
|
} catch {
|
|
12766
12919
|
continue;
|
|
12767
12920
|
}
|
|
12768
12921
|
for (const orgId of orgDirs) {
|
|
12769
|
-
paths.push(
|
|
12922
|
+
paths.push(join25(sessionPath, orgId, "memory", "CLAUDE.md"));
|
|
12770
12923
|
}
|
|
12771
12924
|
}
|
|
12772
12925
|
} catch {}
|
|
@@ -12774,22 +12927,22 @@ function getCoworkMemoryClaudeMdPaths() {
|
|
|
12774
12927
|
}
|
|
12775
12928
|
function findCoworkPluginsDir() {
|
|
12776
12929
|
return walkOrgDirs((orgDir) => {
|
|
12777
|
-
const pluginsDir =
|
|
12778
|
-
return
|
|
12930
|
+
const pluginsDir = join25(orgDir, "cowork_plugins");
|
|
12931
|
+
return existsSync32(join25(pluginsDir, "installed_plugins.json")) ? pluginsDir : null;
|
|
12779
12932
|
});
|
|
12780
12933
|
}
|
|
12781
12934
|
function findRpmPluginByName(pluginName) {
|
|
12782
12935
|
return walkOrgDirs((orgDir) => {
|
|
12783
|
-
const manifestPath =
|
|
12784
|
-
if (!
|
|
12936
|
+
const manifestPath = join25(orgDir, "rpm", "manifest.json");
|
|
12937
|
+
if (!existsSync32(manifestPath))
|
|
12785
12938
|
return null;
|
|
12786
12939
|
try {
|
|
12787
|
-
const manifest = JSON.parse(
|
|
12940
|
+
const manifest = JSON.parse(readFileSync26(manifestPath, "utf-8"));
|
|
12788
12941
|
const entry = manifest.plugins?.find((p) => p.name === pluginName);
|
|
12789
12942
|
if (!entry?.id)
|
|
12790
12943
|
return null;
|
|
12791
12944
|
return {
|
|
12792
|
-
pluginPath:
|
|
12945
|
+
pluginPath: join25(orgDir, "rpm", entry.id),
|
|
12793
12946
|
orgDir
|
|
12794
12947
|
};
|
|
12795
12948
|
} catch {
|
|
@@ -12811,7 +12964,7 @@ function getMarketplaceJson2() {
|
|
|
12811
12964
|
};
|
|
12812
12965
|
}
|
|
12813
12966
|
function getCoworkSettingsPath(pluginsDir) {
|
|
12814
|
-
return
|
|
12967
|
+
return join25(dirname9(pluginsDir), "cowork_settings.json");
|
|
12815
12968
|
}
|
|
12816
12969
|
function setCoworkPluginEnabled(pluginsDir, enabled) {
|
|
12817
12970
|
const settingsPath = getCoworkSettingsPath(pluginsDir);
|
|
@@ -12828,8 +12981,8 @@ class ClaudeDesktopAdapter {
|
|
|
12828
12981
|
async detect() {
|
|
12829
12982
|
const os2 = platform3();
|
|
12830
12983
|
if (os2 === "darwin")
|
|
12831
|
-
return
|
|
12832
|
-
return
|
|
12984
|
+
return existsSync32("/Applications/Claude.app");
|
|
12985
|
+
return existsSync32(getMcpConfigPath());
|
|
12833
12986
|
}
|
|
12834
12987
|
supportsMcpScope(scope) {
|
|
12835
12988
|
return scope === "user";
|
|
@@ -12859,8 +13012,8 @@ class ClaudeDesktopAdapter {
|
|
|
12859
13012
|
}
|
|
12860
13013
|
const pluginsDir = findCoworkPluginsDir();
|
|
12861
13014
|
if (pluginsDir) {
|
|
12862
|
-
const cacheDir =
|
|
12863
|
-
const marketDir =
|
|
13015
|
+
const cacheDir = join25(pluginsDir, "cache", "runwork", PLUGIN_NAME2, PLUGIN_VERSION2);
|
|
13016
|
+
const marketDir = join25(pluginsDir, "marketplaces", "runwork", "plugins", PLUGIN_NAME2);
|
|
12864
13017
|
for (const dir of [cacheDir, marketDir]) {
|
|
12865
13018
|
writePluginMcpConfig(dir, servers);
|
|
12866
13019
|
}
|
|
@@ -12878,14 +13031,14 @@ class ClaudeDesktopAdapter {
|
|
|
12878
13031
|
console.warn(" [Claude Desktop] Cowork plugins directory not found. Open Claude Desktop at least once first.");
|
|
12879
13032
|
return 0;
|
|
12880
13033
|
}
|
|
12881
|
-
const cacheDir =
|
|
12882
|
-
const marketRoot =
|
|
12883
|
-
const marketDir =
|
|
13034
|
+
const cacheDir = join25(pluginsDir, "cache", "runwork", PLUGIN_NAME2, PLUGIN_VERSION2);
|
|
13035
|
+
const marketRoot = join25(pluginsDir, "marketplaces", "runwork");
|
|
13036
|
+
const marketDir = join25(marketRoot, "plugins", PLUGIN_NAME2);
|
|
12884
13037
|
for (const dir of [cacheDir, marketDir]) {
|
|
12885
13038
|
writePluginMetadata(dir, getPluginMetadata());
|
|
12886
13039
|
writePluginSkills(dir, skills);
|
|
12887
13040
|
}
|
|
12888
|
-
const installedPath =
|
|
13041
|
+
const installedPath = join25(pluginsDir, "installed_plugins.json");
|
|
12889
13042
|
const installed = readJsonConfig(installedPath);
|
|
12890
13043
|
if (!installed.version)
|
|
12891
13044
|
installed.version = 2;
|
|
@@ -12899,10 +13052,10 @@ class ClaudeDesktopAdapter {
|
|
|
12899
13052
|
lastUpdated: new Date().toISOString()
|
|
12900
13053
|
}];
|
|
12901
13054
|
writeJsonConfig(installedPath, installed);
|
|
12902
|
-
const marketCatalogDir =
|
|
12903
|
-
|
|
12904
|
-
writeFileSync18(
|
|
12905
|
-
const marketplacesPath =
|
|
13055
|
+
const marketCatalogDir = join25(marketRoot, ".claude-plugin");
|
|
13056
|
+
mkdirSync18(marketCatalogDir, { recursive: true });
|
|
13057
|
+
writeFileSync18(join25(marketCatalogDir, "marketplace.json"), JSON.stringify(getMarketplaceJson2(), null, 2));
|
|
13058
|
+
const marketplacesPath = join25(pluginsDir, "known_marketplaces.json");
|
|
12906
13059
|
const marketplaces = readJsonConfig(marketplacesPath);
|
|
12907
13060
|
marketplaces["runwork"] = {
|
|
12908
13061
|
source: { source: "directory", path: marketRoot },
|
|
@@ -12921,8 +13074,8 @@ class ClaudeDesktopAdapter {
|
|
|
12921
13074
|
}
|
|
12922
13075
|
}
|
|
12923
13076
|
async buildPluginZip(skills, mcpServers, teamInstructions, outputPath) {
|
|
12924
|
-
const stagingRoot = mkdtempSync3(
|
|
12925
|
-
const pluginStagingDir =
|
|
13077
|
+
const stagingRoot = mkdtempSync3(join25(tmpdir3(), "runwork-plugin-"));
|
|
13078
|
+
const pluginStagingDir = join25(stagingRoot, PLUGIN_NAME2);
|
|
12926
13079
|
try {
|
|
12927
13080
|
writePluginTree(pluginStagingDir, {
|
|
12928
13081
|
...getPluginMetadata(),
|
|
@@ -12935,7 +13088,7 @@ class ClaudeDesktopAdapter {
|
|
|
12935
13088
|
createZipFromDir(pluginStagingDir, outputPath);
|
|
12936
13089
|
} finally {
|
|
12937
13090
|
try {
|
|
12938
|
-
|
|
13091
|
+
rmSync7(stagingRoot, { recursive: true, force: true });
|
|
12939
13092
|
} catch {}
|
|
12940
13093
|
}
|
|
12941
13094
|
}
|
|
@@ -12953,15 +13106,15 @@ class ClaudeDesktopAdapter {
|
|
|
12953
13106
|
const pluginsDir = findCoworkPluginsDir();
|
|
12954
13107
|
if (!pluginsDir)
|
|
12955
13108
|
return;
|
|
12956
|
-
writePluginTeamInstructions(
|
|
12957
|
-
writePluginTeamInstructions(
|
|
13109
|
+
writePluginTeamInstructions(join25(pluginsDir, "cache", "runwork", PLUGIN_NAME2, PLUGIN_VERSION2), instructions);
|
|
13110
|
+
writePluginTeamInstructions(join25(pluginsDir, "marketplaces", "runwork", "plugins", PLUGIN_NAME2), instructions);
|
|
12958
13111
|
}
|
|
12959
13112
|
async writeAgentConfig(config, _scope) {
|
|
12960
13113
|
const configPath = getMcpConfigPath();
|
|
12961
13114
|
let desktopConfig = {};
|
|
12962
|
-
if (
|
|
13115
|
+
if (existsSync32(configPath)) {
|
|
12963
13116
|
try {
|
|
12964
|
-
desktopConfig = JSON.parse(
|
|
13117
|
+
desktopConfig = JSON.parse(readFileSync26(configPath, "utf-8"));
|
|
12965
13118
|
} catch {}
|
|
12966
13119
|
}
|
|
12967
13120
|
if (!desktopConfig.preferences)
|
|
@@ -12995,62 +13148,71 @@ class ClaudeDesktopAdapter {
|
|
|
12995
13148
|
removeTeamInstructionsFromFile(path2);
|
|
12996
13149
|
} catch {}
|
|
12997
13150
|
}
|
|
12998
|
-
const
|
|
12999
|
-
|
|
13000
|
-
if (
|
|
13001
|
-
|
|
13002
|
-
|
|
13003
|
-
|
|
13004
|
-
|
|
13005
|
-
|
|
13006
|
-
|
|
13007
|
-
|
|
13008
|
-
|
|
13009
|
-
|
|
13010
|
-
|
|
13011
|
-
|
|
13012
|
-
|
|
13013
|
-
|
|
13014
|
-
|
|
13015
|
-
|
|
13151
|
+
for (const orgDir of listOrgDirs()) {
|
|
13152
|
+
const manifestPath = join25(orgDir, "rpm", "manifest.json");
|
|
13153
|
+
if (!existsSync32(manifestPath))
|
|
13154
|
+
continue;
|
|
13155
|
+
try {
|
|
13156
|
+
const manifest = JSON.parse(readFileSync26(manifestPath, "utf-8"));
|
|
13157
|
+
if (!Array.isArray(manifest.plugins))
|
|
13158
|
+
continue;
|
|
13159
|
+
const ours = manifest.plugins.filter((p) => isRunworkRpmPluginName(p.name));
|
|
13160
|
+
if (!ours.length)
|
|
13161
|
+
continue;
|
|
13162
|
+
for (const entry of ours) {
|
|
13163
|
+
if (!entry.id)
|
|
13164
|
+
continue;
|
|
13165
|
+
moveToTrash(join25(orgDir, "rpm", entry.id), `claude-desktop-plugin:${entry.name}`);
|
|
13166
|
+
}
|
|
13167
|
+
manifest.plugins = manifest.plugins.filter((p) => !isRunworkRpmPluginName(p.name));
|
|
13168
|
+
manifest.lastUpdated = Date.now();
|
|
13169
|
+
writeFileSync18(manifestPath, JSON.stringify(manifest, null, 2));
|
|
13170
|
+
} catch {}
|
|
13016
13171
|
}
|
|
13017
|
-
const
|
|
13018
|
-
|
|
13019
|
-
|
|
13020
|
-
|
|
13172
|
+
for (const orgDir of listOrgDirs()) {
|
|
13173
|
+
const pluginsDir = join25(orgDir, "cowork_plugins");
|
|
13174
|
+
if (!existsSync32(join25(pluginsDir, "installed_plugins.json")))
|
|
13175
|
+
continue;
|
|
13176
|
+
const cacheRunwork = join25(pluginsDir, "cache", "runwork");
|
|
13177
|
+
const marketRunwork = join25(pluginsDir, "marketplaces", "runwork");
|
|
13021
13178
|
for (const dir of [cacheRunwork, marketRunwork]) {
|
|
13022
|
-
if (
|
|
13179
|
+
if (existsSync32(dir)) {
|
|
13023
13180
|
try {
|
|
13024
|
-
|
|
13181
|
+
rmSync7(dir, { recursive: true, force: true });
|
|
13025
13182
|
} catch {}
|
|
13026
13183
|
}
|
|
13027
13184
|
}
|
|
13028
|
-
const installedPath =
|
|
13029
|
-
|
|
13030
|
-
|
|
13031
|
-
|
|
13032
|
-
|
|
13033
|
-
|
|
13034
|
-
|
|
13185
|
+
const installedPath = join25(pluginsDir, "installed_plugins.json");
|
|
13186
|
+
try {
|
|
13187
|
+
const installed = readJsonConfig(installedPath);
|
|
13188
|
+
if (installed.plugins) {
|
|
13189
|
+
for (const key of Object.keys(installed.plugins)) {
|
|
13190
|
+
if (isRunworkPluginKey(key))
|
|
13191
|
+
delete installed.plugins[key];
|
|
13035
13192
|
}
|
|
13036
|
-
|
|
13037
|
-
|
|
13038
|
-
|
|
13039
|
-
|
|
13193
|
+
writeJsonConfig(installedPath, installed);
|
|
13194
|
+
}
|
|
13195
|
+
} catch {}
|
|
13196
|
+
const marketplacesPath = join25(pluginsDir, "known_marketplaces.json");
|
|
13197
|
+
if (existsSync32(marketplacesPath)) {
|
|
13040
13198
|
try {
|
|
13041
13199
|
const marketplaces = readJsonConfig(marketplacesPath);
|
|
13042
|
-
delete marketplaces[
|
|
13200
|
+
delete marketplaces[RUNWORK_PLUGIN_MARKETPLACE];
|
|
13043
13201
|
writeJsonConfig(marketplacesPath, marketplaces);
|
|
13044
13202
|
} catch {}
|
|
13045
13203
|
}
|
|
13046
13204
|
const coworkSettingsPath = getCoworkSettingsPath(pluginsDir);
|
|
13047
|
-
if (
|
|
13205
|
+
if (existsSync32(coworkSettingsPath)) {
|
|
13048
13206
|
try {
|
|
13049
13207
|
const settings = readJsonConfig(coworkSettingsPath);
|
|
13050
13208
|
const enabledPlugins = settings.enabledPlugins;
|
|
13051
13209
|
if (enabledPlugins && typeof enabledPlugins === "object" && !Array.isArray(enabledPlugins)) {
|
|
13052
|
-
|
|
13053
|
-
|
|
13210
|
+
const flags = enabledPlugins;
|
|
13211
|
+
for (const key of Object.keys(flags)) {
|
|
13212
|
+
if (isRunworkPluginKey(key))
|
|
13213
|
+
delete flags[key];
|
|
13214
|
+
}
|
|
13215
|
+
settings.enabledPlugins = flags;
|
|
13054
13216
|
writeJsonConfig(coworkSettingsPath, settings);
|
|
13055
13217
|
}
|
|
13056
13218
|
} catch {}
|
|
@@ -13062,22 +13224,22 @@ class ClaudeDesktopAdapter {
|
|
|
13062
13224
|
const os2 = platform3();
|
|
13063
13225
|
let claudeAppDir;
|
|
13064
13226
|
if (os2 === "darwin") {
|
|
13065
|
-
claudeAppDir =
|
|
13227
|
+
claudeAppDir = join25(homedir7(), "Library", "Application Support", "Claude");
|
|
13066
13228
|
} else if (os2 === "win32") {
|
|
13067
|
-
claudeAppDir =
|
|
13229
|
+
claudeAppDir = join25(process.env.APPDATA || join25(homedir7(), "AppData", "Roaming"), "Claude");
|
|
13068
13230
|
} else {
|
|
13069
|
-
claudeAppDir =
|
|
13231
|
+
claudeAppDir = join25(homedir7(), ".config", "Claude");
|
|
13070
13232
|
}
|
|
13071
|
-
if (!
|
|
13233
|
+
if (!existsSync32(claudeAppDir))
|
|
13072
13234
|
return null;
|
|
13073
13235
|
const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
|
|
13074
13236
|
let sessionCount = 0;
|
|
13075
13237
|
let latestActivity = 0;
|
|
13076
13238
|
const modelsUsed = new Set;
|
|
13077
13239
|
let maxMcpTools = 0;
|
|
13078
|
-
const agentSessionsDir =
|
|
13240
|
+
const agentSessionsDir = join25(claudeAppDir, "local-agent-mode-sessions");
|
|
13079
13241
|
const activeDays = new Set;
|
|
13080
|
-
if (
|
|
13242
|
+
if (existsSync32(agentSessionsDir)) {
|
|
13081
13243
|
this.walkSessionDirs(agentSessionsDir, sinceMs, (session) => {
|
|
13082
13244
|
const sessionTime = session.lastActivityAt ?? session.createdAt ?? 0;
|
|
13083
13245
|
if (sessionTime > sinceMs) {
|
|
@@ -13095,8 +13257,8 @@ class ClaudeDesktopAdapter {
|
|
|
13095
13257
|
}
|
|
13096
13258
|
});
|
|
13097
13259
|
}
|
|
13098
|
-
const codeSessionsDir =
|
|
13099
|
-
if (
|
|
13260
|
+
const codeSessionsDir = join25(claudeAppDir, "claude-code-sessions");
|
|
13261
|
+
if (existsSync32(codeSessionsDir)) {
|
|
13100
13262
|
this.walkSessionDirs(codeSessionsDir, sinceMs, (session) => {
|
|
13101
13263
|
const sessionTime = session.lastActivityAt ?? session.createdAt ?? 0;
|
|
13102
13264
|
if (sessionTime > sinceMs) {
|
|
@@ -13110,10 +13272,10 @@ class ClaudeDesktopAdapter {
|
|
|
13110
13272
|
});
|
|
13111
13273
|
}
|
|
13112
13274
|
let scheduledTaskRuns = 0;
|
|
13113
|
-
const scheduledTasksPath =
|
|
13114
|
-
if (
|
|
13275
|
+
const scheduledTasksPath = join25(claudeAppDir, "scheduled-tasks.json");
|
|
13276
|
+
if (existsSync32(scheduledTasksPath)) {
|
|
13115
13277
|
try {
|
|
13116
|
-
const raw =
|
|
13278
|
+
const raw = readFileSync26(scheduledTasksPath, "utf-8");
|
|
13117
13279
|
const parsed = JSON.parse(raw);
|
|
13118
13280
|
const tasks = Array.isArray(parsed) ? parsed : Object.values(parsed);
|
|
13119
13281
|
for (const task of tasks) {
|
|
@@ -13149,15 +13311,15 @@ class ClaudeDesktopAdapter {
|
|
|
13149
13311
|
const os2 = platform3();
|
|
13150
13312
|
let claudeAppDir;
|
|
13151
13313
|
if (os2 === "darwin") {
|
|
13152
|
-
claudeAppDir =
|
|
13314
|
+
claudeAppDir = join25(homedir7(), "Library", "Application Support", "Claude");
|
|
13153
13315
|
} else if (os2 === "win32") {
|
|
13154
|
-
claudeAppDir =
|
|
13316
|
+
claudeAppDir = join25(process.env.APPDATA || join25(homedir7(), "AppData", "Roaming"), "Claude");
|
|
13155
13317
|
} else {
|
|
13156
|
-
claudeAppDir =
|
|
13318
|
+
claudeAppDir = join25(homedir7(), ".config", "Claude");
|
|
13157
13319
|
}
|
|
13158
|
-
const versionPath =
|
|
13159
|
-
if (
|
|
13160
|
-
return
|
|
13320
|
+
const versionPath = join25(claudeAppDir, "claude-code", "sdk-version");
|
|
13321
|
+
if (existsSync32(versionPath)) {
|
|
13322
|
+
return readFileSync26(versionPath, "utf-8").trim();
|
|
13161
13323
|
}
|
|
13162
13324
|
return null;
|
|
13163
13325
|
} catch {
|
|
@@ -13166,31 +13328,31 @@ class ClaudeDesktopAdapter {
|
|
|
13166
13328
|
}
|
|
13167
13329
|
walkSessionDirs(baseDir, _sinceMs, onSession) {
|
|
13168
13330
|
try {
|
|
13169
|
-
for (const orgDir of
|
|
13331
|
+
for (const orgDir of readdirSync8(baseDir)) {
|
|
13170
13332
|
if (orgDir.startsWith(".") || orgDir === "skills-plugin")
|
|
13171
13333
|
continue;
|
|
13172
|
-
const orgPath =
|
|
13334
|
+
const orgPath = join25(baseDir, orgDir);
|
|
13173
13335
|
try {
|
|
13174
|
-
if (!
|
|
13336
|
+
if (!statSync5(orgPath).isDirectory())
|
|
13175
13337
|
continue;
|
|
13176
13338
|
} catch {
|
|
13177
13339
|
continue;
|
|
13178
13340
|
}
|
|
13179
|
-
for (const userDir of
|
|
13341
|
+
for (const userDir of readdirSync8(orgPath)) {
|
|
13180
13342
|
if (userDir.startsWith("."))
|
|
13181
13343
|
continue;
|
|
13182
|
-
const userPath =
|
|
13344
|
+
const userPath = join25(orgPath, userDir);
|
|
13183
13345
|
try {
|
|
13184
|
-
if (!
|
|
13346
|
+
if (!statSync5(userPath).isDirectory())
|
|
13185
13347
|
continue;
|
|
13186
13348
|
} catch {
|
|
13187
13349
|
continue;
|
|
13188
13350
|
}
|
|
13189
|
-
for (const file of
|
|
13351
|
+
for (const file of readdirSync8(userPath)) {
|
|
13190
13352
|
if (!file.endsWith(".json"))
|
|
13191
13353
|
continue;
|
|
13192
13354
|
try {
|
|
13193
|
-
const session = JSON.parse(
|
|
13355
|
+
const session = JSON.parse(readFileSync26(join25(userPath, file), "utf-8"));
|
|
13194
13356
|
onSession(session);
|
|
13195
13357
|
} catch {
|
|
13196
13358
|
continue;
|
|
@@ -13204,9 +13366,9 @@ class ClaudeDesktopAdapter {
|
|
|
13204
13366
|
|
|
13205
13367
|
// src/agents/cursor.ts
|
|
13206
13368
|
init_subprocess();
|
|
13207
|
-
import { existsSync as
|
|
13208
|
-
import { join as
|
|
13209
|
-
import { homedir as
|
|
13369
|
+
import { existsSync as existsSync33, mkdirSync as mkdirSync19, unlinkSync as unlinkSync4, writeFileSync as writeFileSync19 } from "fs";
|
|
13370
|
+
import { join as join26 } from "path";
|
|
13371
|
+
import { homedir as homedir8, platform as platform4 } from "os";
|
|
13210
13372
|
|
|
13211
13373
|
// src/utils/sqlite-adapter.ts
|
|
13212
13374
|
var adapter = null;
|
|
@@ -13298,7 +13460,7 @@ class CursorAdapter {
|
|
|
13298
13460
|
mcpProvidesSkills = true;
|
|
13299
13461
|
async detect() {
|
|
13300
13462
|
const os2 = platform4();
|
|
13301
|
-
if (os2 === "darwin" &&
|
|
13463
|
+
if (os2 === "darwin" && existsSync33("/Applications/Cursor.app"))
|
|
13302
13464
|
return true;
|
|
13303
13465
|
return !!whichBinary("cursor");
|
|
13304
13466
|
}
|
|
@@ -13309,7 +13471,7 @@ class CursorAdapter {
|
|
|
13309
13471
|
return true;
|
|
13310
13472
|
}
|
|
13311
13473
|
async writeMcpServers(servers, scope) {
|
|
13312
|
-
const filePath = scope === "project" ?
|
|
13474
|
+
const filePath = scope === "project" ? join26(process.cwd(), ".cursor", "mcp.json") : join26(homedir8(), ".cursor", "mcp.json");
|
|
13313
13475
|
const entries = {};
|
|
13314
13476
|
for (const s of servers) {
|
|
13315
13477
|
entries[s.name] = {
|
|
@@ -13322,8 +13484,8 @@ class CursorAdapter {
|
|
|
13322
13484
|
async writeSkills(skills, scope) {
|
|
13323
13485
|
if (scope === "user")
|
|
13324
13486
|
return 0;
|
|
13325
|
-
const rulesDir =
|
|
13326
|
-
|
|
13487
|
+
const rulesDir = join26(process.cwd(), ".cursor", "rules");
|
|
13488
|
+
mkdirSync19(rulesDir, { recursive: true });
|
|
13327
13489
|
for (const skill of skills) {
|
|
13328
13490
|
const mdcContent = `---
|
|
13329
13491
|
description: "${skill.description}"
|
|
@@ -13331,30 +13493,30 @@ alwaysApply: false
|
|
|
13331
13493
|
---
|
|
13332
13494
|
|
|
13333
13495
|
${skill.content}`;
|
|
13334
|
-
writeFileSync19(
|
|
13496
|
+
writeFileSync19(join26(rulesDir, `${skill.filename}.mdc`), mdcContent);
|
|
13335
13497
|
}
|
|
13336
13498
|
return skills.length;
|
|
13337
13499
|
}
|
|
13338
13500
|
async writeInstructionHint(hint, scope) {
|
|
13339
|
-
const filePath = scope === "project" ?
|
|
13501
|
+
const filePath = scope === "project" ? join26(process.cwd(), ".cursor", "rules", "runwork.mdc") : join26(homedir8(), ".cursor", "rules", "runwork.mdc");
|
|
13340
13502
|
const mdcContent = `---
|
|
13341
13503
|
description: "Runwork workspace connection"
|
|
13342
13504
|
alwaysApply: true
|
|
13343
13505
|
---
|
|
13344
13506
|
|
|
13345
13507
|
${hint}`;
|
|
13346
|
-
|
|
13508
|
+
mkdirSync19(join26(filePath, ".."), { recursive: true });
|
|
13347
13509
|
writeFileSync19(filePath, mdcContent);
|
|
13348
13510
|
}
|
|
13349
13511
|
async writeTeamInstructions(instructions, scope) {
|
|
13350
|
-
const filePath = scope === "project" ?
|
|
13512
|
+
const filePath = scope === "project" ? join26(process.cwd(), ".cursor", "rules", "runwork-team.mdc") : join26(homedir8(), ".cursor", "rules", "runwork-team.mdc");
|
|
13351
13513
|
const mdcContent = `---
|
|
13352
13514
|
description: "Team instructions from Runwork workspace"
|
|
13353
13515
|
alwaysApply: true
|
|
13354
13516
|
---
|
|
13355
13517
|
|
|
13356
13518
|
${instructions}`;
|
|
13357
|
-
|
|
13519
|
+
mkdirSync19(join26(filePath, ".."), { recursive: true });
|
|
13358
13520
|
writeFileSync19(filePath, mdcContent);
|
|
13359
13521
|
}
|
|
13360
13522
|
async writeAgentConfig(config, scope, baseline) {
|
|
@@ -13364,7 +13526,7 @@ ${instructions}`;
|
|
|
13364
13526
|
this.mergeSandboxAllowlist(config.networkAllowlist);
|
|
13365
13527
|
}
|
|
13366
13528
|
const globalDbPath = this.globalStorageDbPath();
|
|
13367
|
-
if (!
|
|
13529
|
+
if (!existsSync33(globalDbPath))
|
|
13368
13530
|
return;
|
|
13369
13531
|
const db = openWritableSqlite(globalDbPath);
|
|
13370
13532
|
if (!db) {
|
|
@@ -13400,7 +13562,7 @@ ${instructions}`;
|
|
|
13400
13562
|
return;
|
|
13401
13563
|
}
|
|
13402
13564
|
mergeSandboxAllowlist(domains) {
|
|
13403
|
-
const configPath =
|
|
13565
|
+
const configPath = join26(homedir8(), ".cursor", "sandbox.json");
|
|
13404
13566
|
try {
|
|
13405
13567
|
const config = readJsonConfig(configPath);
|
|
13406
13568
|
if (!config.networkPolicy)
|
|
@@ -13415,29 +13577,25 @@ ${instructions}`;
|
|
|
13415
13577
|
writeJsonConfig(configPath, config);
|
|
13416
13578
|
} catch {}
|
|
13417
13579
|
}
|
|
13580
|
+
async removeSkills(skillFilenames, scope) {
|
|
13581
|
+
if (scope !== "project")
|
|
13582
|
+
return;
|
|
13583
|
+
removeMatchingSkillFiles(join26(process.cwd(), ".cursor", "rules"), new Set(skillFilenames), ".mdc");
|
|
13584
|
+
}
|
|
13418
13585
|
async cleanup(scope, manifest) {
|
|
13419
|
-
const mcpPath = scope === "project" ?
|
|
13586
|
+
const mcpPath = scope === "project" ? join26(process.cwd(), ".cursor", "mcp.json") : join26(homedir8(), ".cursor", "mcp.json");
|
|
13420
13587
|
removeRunworkMcpServers(mcpPath, "mcpServers");
|
|
13421
|
-
const rulesDir = scope === "project" ?
|
|
13422
|
-
if (
|
|
13588
|
+
const rulesDir = scope === "project" ? join26(process.cwd(), ".cursor", "rules") : join26(homedir8(), ".cursor", "rules");
|
|
13589
|
+
if (existsSync33(rulesDir)) {
|
|
13423
13590
|
for (const file of ["runwork.mdc", "runwork-team.mdc"]) {
|
|
13424
|
-
const filePath =
|
|
13425
|
-
if (
|
|
13591
|
+
const filePath = join26(rulesDir, file);
|
|
13592
|
+
if (existsSync33(filePath)) {
|
|
13426
13593
|
try {
|
|
13427
13594
|
unlinkSync4(filePath);
|
|
13428
13595
|
} catch {}
|
|
13429
13596
|
}
|
|
13430
13597
|
}
|
|
13431
|
-
|
|
13432
|
-
const allowed = new Set(manifest.skillFilenames.map((f) => `${f}.mdc`));
|
|
13433
|
-
for (const entry of readdirSync7(rulesDir)) {
|
|
13434
|
-
if (allowed.has(entry)) {
|
|
13435
|
-
try {
|
|
13436
|
-
unlinkSync4(join24(rulesDir, entry));
|
|
13437
|
-
} catch {}
|
|
13438
|
-
}
|
|
13439
|
-
}
|
|
13440
|
-
}
|
|
13598
|
+
await this.removeSkills(manifest?.skillFilenames ?? [], scope);
|
|
13441
13599
|
}
|
|
13442
13600
|
}
|
|
13443
13601
|
async readUsageStats(lastSyncAt) {
|
|
@@ -13454,7 +13612,7 @@ ${instructions}`;
|
|
|
13454
13612
|
let latestActivity = 0;
|
|
13455
13613
|
let agenticSessions = 0;
|
|
13456
13614
|
let chatSessions = 0;
|
|
13457
|
-
if (
|
|
13615
|
+
if (existsSync33(globalDbPath)) {
|
|
13458
13616
|
const composerResult = queryReadonlySqlite(globalDbPath, `SELECT value FROM cursorDiskKV WHERE key LIKE 'composerData:%'`);
|
|
13459
13617
|
if (composerResult) {
|
|
13460
13618
|
for (const line of composerResult.split(`
|
|
@@ -13512,10 +13670,10 @@ ${instructions}`;
|
|
|
13512
13670
|
}
|
|
13513
13671
|
}
|
|
13514
13672
|
const sessionCount = newComposersWithoutId + activeComposerIds.size;
|
|
13515
|
-
const trackingDbPath =
|
|
13673
|
+
const trackingDbPath = join26(homedir8(), ".cursor", "ai-tracking", "ai-code-tracking.db");
|
|
13516
13674
|
let aiCommitCount = 0;
|
|
13517
13675
|
let avgAiPercent = 0;
|
|
13518
|
-
if (
|
|
13676
|
+
if (existsSync33(trackingDbPath)) {
|
|
13519
13677
|
const sinceSec = Math.floor(sinceMs / 1000);
|
|
13520
13678
|
const commitResult = queryReadonlySqlite(trackingDbPath, `SELECT count(*), avg(CAST(v2AiPercentage AS REAL)) FROM scored_commits WHERE scoredAt > ${sinceSec}`);
|
|
13521
13679
|
if (commitResult) {
|
|
@@ -13558,17 +13716,17 @@ ${instructions}`;
|
|
|
13558
13716
|
globalStorageDbPath() {
|
|
13559
13717
|
const os2 = platform4();
|
|
13560
13718
|
if (os2 === "darwin") {
|
|
13561
|
-
return
|
|
13719
|
+
return join26(homedir8(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
13562
13720
|
}
|
|
13563
13721
|
if (os2 === "win32") {
|
|
13564
|
-
return
|
|
13722
|
+
return join26(process.env.APPDATA || join26(homedir8(), "AppData", "Roaming"), "Cursor", "User", "globalStorage", "state.vscdb");
|
|
13565
13723
|
}
|
|
13566
|
-
return
|
|
13724
|
+
return join26(homedir8(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
13567
13725
|
}
|
|
13568
13726
|
async readSessionDigests(sinceISO) {
|
|
13569
13727
|
try {
|
|
13570
13728
|
const dbPath = this.globalStorageDbPath();
|
|
13571
|
-
if (!
|
|
13729
|
+
if (!existsSync33(dbPath) || !sqliteAvailable())
|
|
13572
13730
|
return null;
|
|
13573
13731
|
const composerRows = queryReadonlySqlite(dbPath, `SELECT json_object('id', json_extract(value,'$.composerId'), 'createdAt', json_extract(value,'$.createdAt')) FROM cursorDiskKV WHERE key LIKE 'composerData:%'`);
|
|
13574
13732
|
const bubbleRows = queryReadonlySqlite(dbPath, `SELECT json_object('k', key, 'type', json_extract(value,'$.type'), 'text', substr(json_extract(value,'$.text'),1,2000), 'ts', json_extract(value,'$.createdAt'), 'tool', json_extract(value,'$.toolFormerData.name'), 'status', json_extract(value,'$.toolFormerData.status')) FROM cursorDiskKV WHERE key LIKE 'bubbleId:%'`);
|
|
@@ -13582,17 +13740,17 @@ ${instructions}`;
|
|
|
13582
13740
|
}
|
|
13583
13741
|
|
|
13584
13742
|
// src/agents/windsurf.ts
|
|
13585
|
-
import { existsSync as
|
|
13586
|
-
import { join as
|
|
13587
|
-
import { homedir as
|
|
13743
|
+
import { existsSync as existsSync34, mkdirSync as mkdirSync20, unlinkSync as unlinkSync5, writeFileSync as writeFileSync20 } from "fs";
|
|
13744
|
+
import { join as join27 } from "path";
|
|
13745
|
+
import { homedir as homedir9, platform as platform5 } from "os";
|
|
13588
13746
|
function getWindsurfDataDir() {
|
|
13589
13747
|
if (platform5() === "win32") {
|
|
13590
|
-
return
|
|
13748
|
+
return join27(process.env.APPDATA || join27(homedir9(), "AppData", "Roaming"), "Codeium", "windsurf");
|
|
13591
13749
|
}
|
|
13592
|
-
return
|
|
13750
|
+
return join27(homedir9(), ".codeium", "windsurf");
|
|
13593
13751
|
}
|
|
13594
13752
|
function getConfigPath() {
|
|
13595
|
-
return
|
|
13753
|
+
return join27(getWindsurfDataDir(), "mcp_config.json");
|
|
13596
13754
|
}
|
|
13597
13755
|
|
|
13598
13756
|
class WindsurfAdapter {
|
|
@@ -13601,7 +13759,7 @@ class WindsurfAdapter {
|
|
|
13601
13759
|
mcpProvidesSkills = true;
|
|
13602
13760
|
async detect() {
|
|
13603
13761
|
const os2 = platform5();
|
|
13604
|
-
if (os2 === "darwin" &&
|
|
13762
|
+
if (os2 === "darwin" && existsSync34("/Applications/Windsurf.app"))
|
|
13605
13763
|
return true;
|
|
13606
13764
|
return !!whichBinary("windsurf");
|
|
13607
13765
|
}
|
|
@@ -13624,84 +13782,80 @@ class WindsurfAdapter {
|
|
|
13624
13782
|
async writeSkills(skills, scope) {
|
|
13625
13783
|
if (scope === "user")
|
|
13626
13784
|
return 0;
|
|
13627
|
-
const rulesDir =
|
|
13628
|
-
|
|
13785
|
+
const rulesDir = join27(process.cwd(), ".windsurf", "rules");
|
|
13786
|
+
mkdirSync20(rulesDir, { recursive: true });
|
|
13629
13787
|
for (const skill of skills) {
|
|
13630
13788
|
const content = `---
|
|
13631
13789
|
trigger: manual
|
|
13632
13790
|
---
|
|
13633
13791
|
|
|
13634
13792
|
${skill.content}`;
|
|
13635
|
-
writeFileSync20(
|
|
13793
|
+
writeFileSync20(join27(rulesDir, `${skill.filename}.md`), content);
|
|
13636
13794
|
}
|
|
13637
13795
|
return skills.length;
|
|
13638
13796
|
}
|
|
13639
13797
|
async writeTeamInstructions(instructions, scope) {
|
|
13640
|
-
const filePath = scope === "project" ?
|
|
13798
|
+
const filePath = scope === "project" ? join27(process.cwd(), ".windsurf", "rules", "runwork-team.md") : join27(getWindsurfDataDir(), "rules", "runwork-team.md");
|
|
13641
13799
|
const content = `---
|
|
13642
13800
|
trigger: always_on
|
|
13643
13801
|
description: "Team instructions from Runwork workspace"
|
|
13644
13802
|
---
|
|
13645
13803
|
|
|
13646
13804
|
${instructions}`;
|
|
13647
|
-
|
|
13805
|
+
mkdirSync20(join27(filePath, ".."), { recursive: true });
|
|
13648
13806
|
writeFileSync20(filePath, content);
|
|
13649
13807
|
}
|
|
13650
13808
|
async writeInstructionHint(hint, scope) {
|
|
13651
|
-
const filePath = scope === "project" ?
|
|
13809
|
+
const filePath = scope === "project" ? join27(process.cwd(), ".windsurf", "rules", "runwork.md") : join27(getWindsurfDataDir(), "rules", "runwork.md");
|
|
13652
13810
|
const content = `---
|
|
13653
13811
|
trigger: always
|
|
13654
13812
|
---
|
|
13655
13813
|
|
|
13656
13814
|
${hint}`;
|
|
13657
|
-
|
|
13815
|
+
mkdirSync20(join27(filePath, ".."), { recursive: true });
|
|
13658
13816
|
writeFileSync20(filePath, content);
|
|
13659
13817
|
}
|
|
13818
|
+
async removeSkills(skillFilenames, scope) {
|
|
13819
|
+
if (scope !== "project")
|
|
13820
|
+
return;
|
|
13821
|
+
removeMatchingSkillFiles(join27(process.cwd(), ".windsurf", "rules"), new Set(skillFilenames), ".md");
|
|
13822
|
+
}
|
|
13660
13823
|
async cleanup(scope, manifest) {
|
|
13661
13824
|
if (scope === "user") {
|
|
13662
13825
|
removeRunworkMcpServers(getConfigPath(), "mcpServers");
|
|
13663
13826
|
}
|
|
13664
|
-
const rulesDir = scope === "project" ?
|
|
13665
|
-
if (
|
|
13827
|
+
const rulesDir = scope === "project" ? join27(process.cwd(), ".windsurf", "rules") : join27(getWindsurfDataDir(), "rules");
|
|
13828
|
+
if (existsSync34(rulesDir)) {
|
|
13666
13829
|
for (const file of ["runwork.md", "runwork-team.md"]) {
|
|
13667
|
-
const filePath =
|
|
13668
|
-
if (
|
|
13830
|
+
const filePath = join27(rulesDir, file);
|
|
13831
|
+
if (existsSync34(filePath)) {
|
|
13669
13832
|
try {
|
|
13670
13833
|
unlinkSync5(filePath);
|
|
13671
13834
|
} catch {}
|
|
13672
13835
|
}
|
|
13673
13836
|
}
|
|
13674
|
-
|
|
13675
|
-
const allowed = new Set(manifest.skillFilenames.map((f) => `${f}.md`));
|
|
13676
|
-
for (const entry of readdirSync8(rulesDir)) {
|
|
13677
|
-
if (allowed.has(entry)) {
|
|
13678
|
-
try {
|
|
13679
|
-
unlinkSync5(join25(rulesDir, entry));
|
|
13680
|
-
} catch {}
|
|
13681
|
-
}
|
|
13682
|
-
}
|
|
13683
|
-
}
|
|
13837
|
+
await this.removeSkills(manifest?.skillFilenames ?? [], scope);
|
|
13684
13838
|
}
|
|
13685
13839
|
}
|
|
13686
13840
|
}
|
|
13687
13841
|
|
|
13688
13842
|
// src/agents/codex.ts
|
|
13689
|
-
import { existsSync as
|
|
13690
|
-
import { join as
|
|
13691
|
-
import { homedir as
|
|
13843
|
+
import { existsSync as existsSync37, mkdirSync as mkdirSync21, readdirSync as readdirSync11, readFileSync as readFileSync27, statSync as statSync6, writeFileSync as writeFileSync21 } from "fs";
|
|
13844
|
+
import { join as join30 } from "path";
|
|
13845
|
+
import { homedir as homedir12 } from "os";
|
|
13692
13846
|
import { parse, stringify } from "smol-toml";
|
|
13693
13847
|
|
|
13694
13848
|
// src/agents/detection.ts
|
|
13695
13849
|
import { execFile } from "child_process";
|
|
13696
|
-
import { existsSync as
|
|
13697
|
-
import { homedir as
|
|
13698
|
-
import { isAbsolute as isAbsolute2, join as
|
|
13850
|
+
import { existsSync as existsSync36 } from "fs";
|
|
13851
|
+
import { homedir as homedir11, platform as platform7 } from "os";
|
|
13852
|
+
import { isAbsolute as isAbsolute2, join as join29 } from "path";
|
|
13699
13853
|
import { promisify } from "util";
|
|
13700
13854
|
|
|
13701
13855
|
// src/agents/registry.ts
|
|
13702
|
-
import { platform as platform6, homedir as
|
|
13703
|
-
import { isAbsolute, join as
|
|
13704
|
-
import { existsSync as
|
|
13856
|
+
import { platform as platform6, homedir as homedir10 } from "os";
|
|
13857
|
+
import { isAbsolute, join as join28 } from "path";
|
|
13858
|
+
import { existsSync as existsSync35 } from "fs";
|
|
13705
13859
|
|
|
13706
13860
|
// src/agents/registry-data.ts
|
|
13707
13861
|
var CLAUDE_CODE_NATIVE_INSTALL_PATHS = [
|
|
@@ -14298,7 +14452,7 @@ function resolveToAbsolute(ps, scope) {
|
|
|
14298
14452
|
const resolved = resolvePlatformString(ps);
|
|
14299
14453
|
if (!resolved)
|
|
14300
14454
|
return;
|
|
14301
|
-
return scope === "global" ?
|
|
14455
|
+
return scope === "global" ? join28(homedir10(), resolved) : join28(process.cwd(), resolved);
|
|
14302
14456
|
}
|
|
14303
14457
|
function resolveAgentCliCommand(slug) {
|
|
14304
14458
|
const agent = getAgent(slug);
|
|
@@ -14311,8 +14465,8 @@ function resolveAgentCliCommand(slug) {
|
|
|
14311
14465
|
const resolved = resolvePlatformString(candidate);
|
|
14312
14466
|
if (!resolved)
|
|
14313
14467
|
continue;
|
|
14314
|
-
const absolute = isAbsolute(resolved) ? resolved :
|
|
14315
|
-
if (
|
|
14468
|
+
const absolute = isAbsolute(resolved) ? resolved : join28(homedir10(), resolved);
|
|
14469
|
+
if (existsSync35(absolute))
|
|
14316
14470
|
return absolute;
|
|
14317
14471
|
}
|
|
14318
14472
|
return null;
|
|
@@ -14355,13 +14509,13 @@ function resolveDetectionPath(target) {
|
|
|
14355
14509
|
const resolved = resolvePlatformString(target);
|
|
14356
14510
|
if (!resolved)
|
|
14357
14511
|
return null;
|
|
14358
|
-
return isAbsolute2(resolved) ? resolved :
|
|
14512
|
+
return isAbsolute2(resolved) ? resolved : join29(homedir11(), resolved);
|
|
14359
14513
|
}
|
|
14360
14514
|
function checkPath(target) {
|
|
14361
14515
|
const absolute = resolveDetectionPath(target);
|
|
14362
14516
|
if (!absolute)
|
|
14363
14517
|
return null;
|
|
14364
|
-
return
|
|
14518
|
+
return existsSync36(absolute) ? absolute : null;
|
|
14365
14519
|
}
|
|
14366
14520
|
async function checkMacosBundleId(target) {
|
|
14367
14521
|
if (platform7() !== "darwin")
|
|
@@ -14441,10 +14595,10 @@ class CodexAdapter {
|
|
|
14441
14595
|
return true;
|
|
14442
14596
|
}
|
|
14443
14597
|
async writeMcpServers(servers, _scope) {
|
|
14444
|
-
const configPath =
|
|
14598
|
+
const configPath = join30(homedir12(), ".codex", "config.toml");
|
|
14445
14599
|
let parsed = {};
|
|
14446
|
-
if (
|
|
14447
|
-
parsed = parse(
|
|
14600
|
+
if (existsSync37(configPath)) {
|
|
14601
|
+
parsed = parse(readFileSync27(configPath, "utf-8"));
|
|
14448
14602
|
}
|
|
14449
14603
|
if (!parsed.mcp_servers || typeof parsed.mcp_servers !== "object") {
|
|
14450
14604
|
parsed.mcp_servers = {};
|
|
@@ -14467,48 +14621,39 @@ class CodexAdapter {
|
|
|
14467
14621
|
}
|
|
14468
14622
|
mcpServers[safeName] = entry;
|
|
14469
14623
|
}
|
|
14470
|
-
|
|
14624
|
+
mkdirSync21(join30(configPath, ".."), { recursive: true });
|
|
14471
14625
|
writeFileSync21(configPath, stringify(parsed));
|
|
14472
14626
|
}
|
|
14473
14627
|
async writeSkills(skills, scope) {
|
|
14474
|
-
const root = scope === "project" ? process.cwd() :
|
|
14475
|
-
const baseDir =
|
|
14476
|
-
const legacyBaseDir =
|
|
14628
|
+
const root = scope === "project" ? process.cwd() : homedir12();
|
|
14629
|
+
const baseDir = join30(root, ".agents", "skills");
|
|
14630
|
+
const legacyBaseDir = join30(root, ".codex", "skills");
|
|
14477
14631
|
for (const skill of skills) {
|
|
14478
14632
|
if (skill.name !== skill.filename) {
|
|
14479
|
-
for (const dir of [
|
|
14480
|
-
|
|
14481
|
-
try {
|
|
14482
|
-
rmSync8(dir, { recursive: true, force: true });
|
|
14483
|
-
} catch {}
|
|
14484
|
-
}
|
|
14633
|
+
for (const dir of [join30(baseDir, skill.name), join30(legacyBaseDir, skill.name)]) {
|
|
14634
|
+
moveToTrash(dir, `skill renamed to ${skill.filename}`);
|
|
14485
14635
|
}
|
|
14486
14636
|
}
|
|
14487
|
-
|
|
14488
|
-
|
|
14489
|
-
|
|
14490
|
-
|
|
14491
|
-
} catch {}
|
|
14492
|
-
}
|
|
14493
|
-
const skillDir = join28(baseDir, skill.filename);
|
|
14494
|
-
mkdirSync20(skillDir, { recursive: true });
|
|
14495
|
-
writeFileSync21(join28(skillDir, "SKILL.md"), buildSkillMd2(skill));
|
|
14637
|
+
moveToTrash(join30(legacyBaseDir, skill.filename), "duplicate skill root consolidated");
|
|
14638
|
+
const skillDir = join30(baseDir, skill.filename);
|
|
14639
|
+
mkdirSync21(skillDir, { recursive: true });
|
|
14640
|
+
writeFileSync21(join30(skillDir, "SKILL.md"), buildSkillMd2(skill));
|
|
14496
14641
|
}
|
|
14497
14642
|
return skills.length;
|
|
14498
14643
|
}
|
|
14499
14644
|
async writeInstructionHint(hint, scope) {
|
|
14500
|
-
const filePath = scope === "project" ?
|
|
14645
|
+
const filePath = scope === "project" ? join30(process.cwd(), "AGENTS.md") : join30(homedir12(), ".codex", "AGENTS.md");
|
|
14501
14646
|
writeHintToFile(filePath, hint);
|
|
14502
14647
|
}
|
|
14503
14648
|
async writeTeamInstructions(instructions, scope) {
|
|
14504
|
-
const filePath = scope === "project" ?
|
|
14649
|
+
const filePath = scope === "project" ? join30(process.cwd(), "AGENTS.md") : join30(homedir12(), ".codex", "AGENTS.md");
|
|
14505
14650
|
writeTeamInstructionsToFile(filePath, instructions);
|
|
14506
14651
|
}
|
|
14507
14652
|
async writeAgentConfig(config, scope) {
|
|
14508
|
-
const configPath = scope === "project" ?
|
|
14653
|
+
const configPath = scope === "project" ? join30(process.cwd(), ".codex", "config.toml") : join30(homedir12(), ".codex", "config.toml");
|
|
14509
14654
|
let parsed = {};
|
|
14510
|
-
if (
|
|
14511
|
-
parsed = parse(
|
|
14655
|
+
if (existsSync37(configPath)) {
|
|
14656
|
+
parsed = parse(readFileSync27(configPath, "utf-8"));
|
|
14512
14657
|
}
|
|
14513
14658
|
if (config.modelPreference) {
|
|
14514
14659
|
parsed.model = config.modelPreference;
|
|
@@ -14551,14 +14696,23 @@ class CodexAdapter {
|
|
|
14551
14696
|
sww.network_access = true;
|
|
14552
14697
|
}
|
|
14553
14698
|
}
|
|
14554
|
-
|
|
14699
|
+
mkdirSync21(join30(configPath, ".."), { recursive: true });
|
|
14555
14700
|
writeFileSync21(configPath, stringify(parsed));
|
|
14556
14701
|
}
|
|
14702
|
+
async removeSkills(skillFilenames, scope) {
|
|
14703
|
+
if (!skillFilenames.length)
|
|
14704
|
+
return;
|
|
14705
|
+
const allowed = new Set(skillFilenames);
|
|
14706
|
+
const root = scope === "project" ? process.cwd() : homedir12();
|
|
14707
|
+
for (const skillsDir of [join30(root, ".agents", "skills"), join30(root, ".codex", "skills")]) {
|
|
14708
|
+
removeMatchingSkillDirs(skillsDir, allowed);
|
|
14709
|
+
}
|
|
14710
|
+
}
|
|
14557
14711
|
async cleanup(scope, manifest) {
|
|
14558
|
-
const configPath =
|
|
14559
|
-
if (
|
|
14712
|
+
const configPath = join30(homedir12(), ".codex", "config.toml");
|
|
14713
|
+
if (existsSync37(configPath)) {
|
|
14560
14714
|
try {
|
|
14561
|
-
const parsed = parse(
|
|
14715
|
+
const parsed = parse(readFileSync27(configPath, "utf-8"));
|
|
14562
14716
|
if (parsed.mcp_servers && typeof parsed.mcp_servers === "object") {
|
|
14563
14717
|
const mcpServers = parsed.mcp_servers;
|
|
14564
14718
|
for (const key of Object.keys(mcpServers)) {
|
|
@@ -14570,27 +14724,15 @@ class CodexAdapter {
|
|
|
14570
14724
|
writeFileSync21(configPath, stringify(parsed));
|
|
14571
14725
|
} catch {}
|
|
14572
14726
|
}
|
|
14573
|
-
|
|
14574
|
-
|
|
14575
|
-
if (!existsSync34(skillsDir) || !manifest?.skillFilenames.length)
|
|
14576
|
-
continue;
|
|
14577
|
-
const allowed = new Set(manifest.skillFilenames);
|
|
14578
|
-
for (const entry of readdirSync9(skillsDir)) {
|
|
14579
|
-
if (!allowed.has(entry))
|
|
14580
|
-
continue;
|
|
14581
|
-
try {
|
|
14582
|
-
rmSync8(join28(skillsDir, entry), { recursive: true, force: true });
|
|
14583
|
-
} catch {}
|
|
14584
|
-
}
|
|
14585
|
-
}
|
|
14586
|
-
const instructionFile = scope === "project" ? join28(process.cwd(), "AGENTS.md") : join28(homedir11(), ".codex", "AGENTS.md");
|
|
14727
|
+
await this.removeSkills(manifest?.skillFilenames ?? [], scope);
|
|
14728
|
+
const instructionFile = scope === "project" ? join30(process.cwd(), "AGENTS.md") : join30(homedir12(), ".codex", "AGENTS.md");
|
|
14587
14729
|
removeHintFromFile(instructionFile);
|
|
14588
14730
|
removeTeamInstructionsFromFile(instructionFile);
|
|
14589
14731
|
}
|
|
14590
14732
|
async readUsageStats(lastSyncAt) {
|
|
14591
14733
|
try {
|
|
14592
|
-
const codexDir =
|
|
14593
|
-
if (!
|
|
14734
|
+
const codexDir = join30(homedir12(), ".codex");
|
|
14735
|
+
if (!existsSync37(codexDir))
|
|
14594
14736
|
return null;
|
|
14595
14737
|
const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
|
|
14596
14738
|
const sinceSec = Math.floor(sinceMs / 1000);
|
|
@@ -14599,8 +14741,8 @@ class CodexAdapter {
|
|
|
14599
14741
|
let tokensUsed = rollout.tokensUsed;
|
|
14600
14742
|
let latestMs = rollout.latestMs;
|
|
14601
14743
|
let versions = [];
|
|
14602
|
-
const dbPath =
|
|
14603
|
-
if (
|
|
14744
|
+
const dbPath = join30(codexDir, "state_5.sqlite");
|
|
14745
|
+
if (existsSync37(dbPath)) {
|
|
14604
14746
|
const countResult = queryReadonlySqlite(dbPath, `SELECT count(*) FROM threads WHERE updated_at > ${sinceSec}`);
|
|
14605
14747
|
const dbSessionCount = parseInt(countResult) || 0;
|
|
14606
14748
|
const tokensResult = queryReadonlySqlite(dbPath, `SELECT coalesce(sum(tokens_used), 0) FROM threads WHERE updated_at > ${sinceSec}`);
|
|
@@ -14617,9 +14759,9 @@ class CodexAdapter {
|
|
|
14617
14759
|
}
|
|
14618
14760
|
let messageCount = rollout.messageCount;
|
|
14619
14761
|
if (messageCount === 0) {
|
|
14620
|
-
const historyPath =
|
|
14621
|
-
if (
|
|
14622
|
-
const content =
|
|
14762
|
+
const historyPath = join30(codexDir, "history.jsonl");
|
|
14763
|
+
if (existsSync37(historyPath)) {
|
|
14764
|
+
const content = readFileSync27(historyPath, "utf-8").trim();
|
|
14623
14765
|
if (content) {
|
|
14624
14766
|
for (const line of content.split(/[\r\n]+/)) {
|
|
14625
14767
|
try {
|
|
@@ -14662,19 +14804,19 @@ class CodexAdapter {
|
|
|
14662
14804
|
latestMs: 0,
|
|
14663
14805
|
activeDays: []
|
|
14664
14806
|
};
|
|
14665
|
-
const sessionsDir =
|
|
14666
|
-
if (!
|
|
14807
|
+
const sessionsDir = join30(homedir12(), ".codex", "sessions");
|
|
14808
|
+
if (!existsSync37(sessionsDir))
|
|
14667
14809
|
return result;
|
|
14668
14810
|
const files = [];
|
|
14669
14811
|
const walk = (dir) => {
|
|
14670
14812
|
let entries;
|
|
14671
14813
|
try {
|
|
14672
|
-
entries =
|
|
14814
|
+
entries = readdirSync11(dir, { withFileTypes: true });
|
|
14673
14815
|
} catch {
|
|
14674
14816
|
return;
|
|
14675
14817
|
}
|
|
14676
14818
|
for (const e of entries) {
|
|
14677
|
-
const full =
|
|
14819
|
+
const full = join30(dir, e.name);
|
|
14678
14820
|
if (e.isDirectory())
|
|
14679
14821
|
walk(full);
|
|
14680
14822
|
else if (e.name.startsWith("rollout-") && e.name.endsWith(".jsonl"))
|
|
@@ -14685,7 +14827,7 @@ class CodexAdapter {
|
|
|
14685
14827
|
for (const file of files) {
|
|
14686
14828
|
let stat;
|
|
14687
14829
|
try {
|
|
14688
|
-
stat =
|
|
14830
|
+
stat = statSync6(file);
|
|
14689
14831
|
} catch {
|
|
14690
14832
|
continue;
|
|
14691
14833
|
}
|
|
@@ -14693,7 +14835,7 @@ class CodexAdapter {
|
|
|
14693
14835
|
continue;
|
|
14694
14836
|
let content;
|
|
14695
14837
|
try {
|
|
14696
|
-
content =
|
|
14838
|
+
content = readFileSync27(file, "utf-8");
|
|
14697
14839
|
} catch {
|
|
14698
14840
|
continue;
|
|
14699
14841
|
}
|
|
@@ -14752,20 +14894,20 @@ class CodexAdapter {
|
|
|
14752
14894
|
}
|
|
14753
14895
|
async readSessionDigests(sinceISO) {
|
|
14754
14896
|
try {
|
|
14755
|
-
const sessionsDir =
|
|
14756
|
-
if (!
|
|
14897
|
+
const sessionsDir = join30(homedir12(), ".codex", "sessions");
|
|
14898
|
+
if (!existsSync37(sessionsDir))
|
|
14757
14899
|
return null;
|
|
14758
14900
|
const sinceMs = sinceISO ? new Date(sinceISO).getTime() : 0;
|
|
14759
14901
|
const files = [];
|
|
14760
14902
|
const walk = (dir) => {
|
|
14761
14903
|
let entries;
|
|
14762
14904
|
try {
|
|
14763
|
-
entries =
|
|
14905
|
+
entries = readdirSync11(dir, { withFileTypes: true });
|
|
14764
14906
|
} catch {
|
|
14765
14907
|
return;
|
|
14766
14908
|
}
|
|
14767
14909
|
for (const e of entries) {
|
|
14768
|
-
const full =
|
|
14910
|
+
const full = join30(dir, e.name);
|
|
14769
14911
|
if (e.isDirectory())
|
|
14770
14912
|
walk(full);
|
|
14771
14913
|
else if (e.name.startsWith("rollout-") && e.name.endsWith(".jsonl"))
|
|
@@ -14777,7 +14919,7 @@ class CodexAdapter {
|
|
|
14777
14919
|
for (const file of files) {
|
|
14778
14920
|
let stat;
|
|
14779
14921
|
try {
|
|
14780
|
-
stat =
|
|
14922
|
+
stat = statSync6(file);
|
|
14781
14923
|
} catch {
|
|
14782
14924
|
continue;
|
|
14783
14925
|
}
|
|
@@ -14785,7 +14927,7 @@ class CodexAdapter {
|
|
|
14785
14927
|
continue;
|
|
14786
14928
|
let content;
|
|
14787
14929
|
try {
|
|
14788
|
-
content =
|
|
14930
|
+
content = readFileSync27(file, "utf-8");
|
|
14789
14931
|
} catch {
|
|
14790
14932
|
continue;
|
|
14791
14933
|
}
|
|
@@ -14800,9 +14942,9 @@ class CodexAdapter {
|
|
|
14800
14942
|
}
|
|
14801
14943
|
async readVersion() {
|
|
14802
14944
|
try {
|
|
14803
|
-
const versionPath =
|
|
14804
|
-
if (
|
|
14805
|
-
const data = JSON.parse(
|
|
14945
|
+
const versionPath = join30(homedir12(), ".codex", "version.json");
|
|
14946
|
+
if (existsSync37(versionPath)) {
|
|
14947
|
+
const data = JSON.parse(readFileSync27(versionPath, "utf-8"));
|
|
14806
14948
|
return data.latest_version ?? null;
|
|
14807
14949
|
}
|
|
14808
14950
|
} catch {}
|
|
@@ -14810,24 +14952,24 @@ class CodexAdapter {
|
|
|
14810
14952
|
}
|
|
14811
14953
|
async readSkillUsage(lastSyncAt) {
|
|
14812
14954
|
try {
|
|
14813
|
-
const sessionsDir =
|
|
14814
|
-
if (!
|
|
14955
|
+
const sessionsDir = join30(homedir12(), ".codex", "sessions");
|
|
14956
|
+
if (!existsSync37(sessionsDir))
|
|
14815
14957
|
return null;
|
|
14816
14958
|
const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
|
|
14817
14959
|
const skillCounts = new Map;
|
|
14818
14960
|
const walkDir2 = (dir) => {
|
|
14819
14961
|
let entries;
|
|
14820
14962
|
try {
|
|
14821
|
-
entries =
|
|
14963
|
+
entries = readdirSync11(dir);
|
|
14822
14964
|
} catch {
|
|
14823
14965
|
return;
|
|
14824
14966
|
}
|
|
14825
14967
|
for (const entry of entries) {
|
|
14826
|
-
const fullPath =
|
|
14968
|
+
const fullPath = join30(dir, entry);
|
|
14827
14969
|
if (entry.endsWith(".jsonl")) {
|
|
14828
14970
|
let fileStat;
|
|
14829
14971
|
try {
|
|
14830
|
-
fileStat =
|
|
14972
|
+
fileStat = statSync6(fullPath);
|
|
14831
14973
|
} catch {
|
|
14832
14974
|
continue;
|
|
14833
14975
|
}
|
|
@@ -14836,7 +14978,7 @@ class CodexAdapter {
|
|
|
14836
14978
|
this.parseRolloutForSkills(fullPath, sinceMs, skillCounts);
|
|
14837
14979
|
} else {
|
|
14838
14980
|
try {
|
|
14839
|
-
if (
|
|
14981
|
+
if (statSync6(fullPath).isDirectory())
|
|
14840
14982
|
walkDir2(fullPath);
|
|
14841
14983
|
} catch {
|
|
14842
14984
|
continue;
|
|
@@ -14863,7 +15005,7 @@ class CodexAdapter {
|
|
|
14863
15005
|
parseRolloutForSkills(filePath, sinceMs, skillCounts) {
|
|
14864
15006
|
let content;
|
|
14865
15007
|
try {
|
|
14866
|
-
content =
|
|
15008
|
+
content = readFileSync27(filePath, "utf-8");
|
|
14867
15009
|
} catch {
|
|
14868
15010
|
return;
|
|
14869
15011
|
}
|
|
@@ -14912,11 +15054,11 @@ class CodexAdapter {
|
|
|
14912
15054
|
}
|
|
14913
15055
|
}
|
|
14914
15056
|
registerDesktopWorkspace(workspacePath, label) {
|
|
14915
|
-
const statePath =
|
|
15057
|
+
const statePath = join30(homedir12(), ".codex", ".codex-global-state.json");
|
|
14916
15058
|
let state = {};
|
|
14917
|
-
if (
|
|
15059
|
+
if (existsSync37(statePath)) {
|
|
14918
15060
|
try {
|
|
14919
|
-
state = JSON.parse(
|
|
15061
|
+
state = JSON.parse(readFileSync27(statePath, "utf-8"));
|
|
14920
15062
|
} catch {
|
|
14921
15063
|
return "app_running";
|
|
14922
15064
|
}
|
|
@@ -14943,7 +15085,7 @@ class CodexAdapter {
|
|
|
14943
15085
|
}
|
|
14944
15086
|
labels[workspacePath] = label;
|
|
14945
15087
|
state["electron-workspace-root-labels"] = labels;
|
|
14946
|
-
|
|
15088
|
+
mkdirSync21(join30(statePath, ".."), { recursive: true });
|
|
14947
15089
|
writeFileSync21(statePath, JSON.stringify(state));
|
|
14948
15090
|
return "written";
|
|
14949
15091
|
}
|
|
@@ -14964,9 +15106,9 @@ class CodexDesktopAdapter extends CodexAdapter {
|
|
|
14964
15106
|
}
|
|
14965
15107
|
|
|
14966
15108
|
// src/agents/cline.ts
|
|
14967
|
-
import { existsSync as
|
|
14968
|
-
import { join as
|
|
14969
|
-
import { homedir as
|
|
15109
|
+
import { existsSync as existsSync38, mkdirSync as mkdirSync22, readFileSync as readFileSync28, readdirSync as readdirSync12, rmSync as rmSync10, unlinkSync as unlinkSync6, writeFileSync as writeFileSync22 } from "fs";
|
|
15110
|
+
import { join as join31 } from "path";
|
|
15111
|
+
import { homedir as homedir13 } from "os";
|
|
14970
15112
|
class ClineAdapter {
|
|
14971
15113
|
name = "Cline";
|
|
14972
15114
|
slug = "cline";
|
|
@@ -14981,7 +15123,7 @@ class ClineAdapter {
|
|
|
14981
15123
|
return true;
|
|
14982
15124
|
}
|
|
14983
15125
|
async writeMcpServers(servers, _scope) {
|
|
14984
|
-
const configPath =
|
|
15126
|
+
const configPath = join31(homedir13(), ".cline", "data", "settings", "cline_mcp_settings.json");
|
|
14985
15127
|
const entries = {};
|
|
14986
15128
|
for (const s of servers) {
|
|
14987
15129
|
entries[s.name] = {
|
|
@@ -14994,35 +15136,35 @@ class ClineAdapter {
|
|
|
14994
15136
|
async writeSkills(skills, scope) {
|
|
14995
15137
|
if (scope === "user")
|
|
14996
15138
|
return 0;
|
|
14997
|
-
const rulesDir =
|
|
14998
|
-
|
|
15139
|
+
const rulesDir = join31(process.cwd(), ".clinerules");
|
|
15140
|
+
mkdirSync22(rulesDir, { recursive: true });
|
|
14999
15141
|
for (const skill of skills) {
|
|
15000
|
-
writeFileSync22(
|
|
15142
|
+
writeFileSync22(join31(rulesDir, `${skill.filename}.md`), buildSkillMd2(skill));
|
|
15001
15143
|
}
|
|
15002
15144
|
return skills.length;
|
|
15003
15145
|
}
|
|
15004
15146
|
async writeInstructionHint(hint, scope) {
|
|
15005
15147
|
if (scope === "user")
|
|
15006
15148
|
return;
|
|
15007
|
-
const filePath =
|
|
15008
|
-
|
|
15149
|
+
const filePath = join31(process.cwd(), ".clinerules", "runwork.md");
|
|
15150
|
+
mkdirSync22(join31(filePath, ".."), { recursive: true });
|
|
15009
15151
|
writeFileSync22(filePath, hint);
|
|
15010
15152
|
}
|
|
15011
15153
|
async writeTeamInstructions(instructions, scope) {
|
|
15012
15154
|
if (scope === "user")
|
|
15013
15155
|
return;
|
|
15014
|
-
const filePath =
|
|
15015
|
-
|
|
15156
|
+
const filePath = join31(process.cwd(), ".clinerules", "runwork-team.md");
|
|
15157
|
+
mkdirSync22(join31(filePath, ".."), { recursive: true });
|
|
15016
15158
|
writeFileSync22(filePath, instructions);
|
|
15017
15159
|
}
|
|
15018
15160
|
async writeAgentConfig(config, scope) {
|
|
15019
15161
|
if (scope !== "user")
|
|
15020
15162
|
return;
|
|
15021
|
-
const globalStatePath =
|
|
15163
|
+
const globalStatePath = join31(homedir13(), ".cline", "data", "globalState.json");
|
|
15022
15164
|
let state = {};
|
|
15023
|
-
if (
|
|
15165
|
+
if (existsSync38(globalStatePath)) {
|
|
15024
15166
|
try {
|
|
15025
|
-
state = JSON.parse(
|
|
15167
|
+
state = JSON.parse(readFileSync28(globalStatePath, "utf-8"));
|
|
15026
15168
|
} catch {}
|
|
15027
15169
|
}
|
|
15028
15170
|
if (config.modelPreference) {
|
|
@@ -15043,38 +15185,34 @@ class ClineAdapter {
|
|
|
15043
15185
|
state.autoApprovalSettings.enabled = false;
|
|
15044
15186
|
}
|
|
15045
15187
|
}
|
|
15046
|
-
|
|
15188
|
+
mkdirSync22(join31(globalStatePath, ".."), { recursive: true });
|
|
15047
15189
|
writeFileSync22(globalStatePath, JSON.stringify(state, null, 2));
|
|
15048
15190
|
}
|
|
15191
|
+
async removeSkills(skillFilenames, scope) {
|
|
15192
|
+
if (scope !== "project")
|
|
15193
|
+
return;
|
|
15194
|
+
removeMatchingSkillFiles(join31(process.cwd(), ".clinerules"), new Set(skillFilenames), ".md");
|
|
15195
|
+
}
|
|
15049
15196
|
async cleanup(scope, manifest) {
|
|
15050
15197
|
if (scope === "user") {
|
|
15051
|
-
const configPath =
|
|
15198
|
+
const configPath = join31(homedir13(), ".cline", "data", "settings", "cline_mcp_settings.json");
|
|
15052
15199
|
removeRunworkMcpServers(configPath, "mcpServers");
|
|
15053
15200
|
}
|
|
15054
15201
|
if (scope === "project") {
|
|
15055
|
-
const rulesDir =
|
|
15056
|
-
if (
|
|
15202
|
+
const rulesDir = join31(process.cwd(), ".clinerules");
|
|
15203
|
+
if (existsSync38(rulesDir)) {
|
|
15057
15204
|
for (const file of ["runwork.md", "runwork-team.md"]) {
|
|
15058
|
-
const filePath =
|
|
15059
|
-
if (
|
|
15205
|
+
const filePath = join31(rulesDir, file);
|
|
15206
|
+
if (existsSync38(filePath)) {
|
|
15060
15207
|
try {
|
|
15061
15208
|
unlinkSync6(filePath);
|
|
15062
15209
|
} catch {}
|
|
15063
15210
|
}
|
|
15064
15211
|
}
|
|
15065
|
-
|
|
15066
|
-
const allowed = new Set(manifest.skillFilenames.map((f) => `${f}.md`));
|
|
15067
|
-
for (const entry of readdirSync10(rulesDir)) {
|
|
15068
|
-
if (allowed.has(entry)) {
|
|
15069
|
-
try {
|
|
15070
|
-
unlinkSync6(join29(rulesDir, entry));
|
|
15071
|
-
} catch {}
|
|
15072
|
-
}
|
|
15073
|
-
}
|
|
15074
|
-
}
|
|
15212
|
+
await this.removeSkills(manifest?.skillFilenames ?? [], scope);
|
|
15075
15213
|
try {
|
|
15076
|
-
if (
|
|
15077
|
-
|
|
15214
|
+
if (readdirSync12(rulesDir).length === 0)
|
|
15215
|
+
rmSync10(rulesDir, { recursive: true });
|
|
15078
15216
|
} catch {}
|
|
15079
15217
|
}
|
|
15080
15218
|
}
|
|
@@ -15082,9 +15220,9 @@ class ClineAdapter {
|
|
|
15082
15220
|
}
|
|
15083
15221
|
|
|
15084
15222
|
// src/agents/gemini.ts
|
|
15085
|
-
import { existsSync as
|
|
15086
|
-
import { join as
|
|
15087
|
-
import { homedir as
|
|
15223
|
+
import { existsSync as existsSync39, mkdirSync as mkdirSync23, readdirSync as readdirSync13, readFileSync as readFileSync29, statSync as statSync7, writeFileSync as writeFileSync23 } from "fs";
|
|
15224
|
+
import { join as join32 } from "path";
|
|
15225
|
+
import { homedir as homedir14 } from "os";
|
|
15088
15226
|
class GeminiAdapter {
|
|
15089
15227
|
name = "Gemini CLI";
|
|
15090
15228
|
slug = "gemini";
|
|
@@ -15099,7 +15237,7 @@ class GeminiAdapter {
|
|
|
15099
15237
|
return true;
|
|
15100
15238
|
}
|
|
15101
15239
|
async writeMcpServers(servers, _scope) {
|
|
15102
|
-
const configPath =
|
|
15240
|
+
const configPath = join32(homedir14(), ".gemini", "settings.json");
|
|
15103
15241
|
const entries = {};
|
|
15104
15242
|
for (const s of servers) {
|
|
15105
15243
|
entries[s.name] = {
|
|
@@ -15110,36 +15248,31 @@ class GeminiAdapter {
|
|
|
15110
15248
|
mergeJsonMcpServers(configPath, entries, "mcpServers");
|
|
15111
15249
|
}
|
|
15112
15250
|
async writeSkills(skills, scope) {
|
|
15113
|
-
const baseDir = scope === "project" ?
|
|
15251
|
+
const baseDir = scope === "project" ? join32(process.cwd(), ".gemini", "skills") : join32(homedir14(), ".gemini", "skills");
|
|
15114
15252
|
for (const skill of skills) {
|
|
15115
15253
|
if (skill.name !== skill.filename) {
|
|
15116
|
-
|
|
15117
|
-
if (existsSync36(oldDir)) {
|
|
15118
|
-
try {
|
|
15119
|
-
rmSync10(oldDir, { recursive: true, force: true });
|
|
15120
|
-
} catch {}
|
|
15121
|
-
}
|
|
15254
|
+
moveToTrash(join32(baseDir, skill.name), `skill renamed to ${skill.filename}`);
|
|
15122
15255
|
}
|
|
15123
|
-
const skillDir =
|
|
15124
|
-
|
|
15125
|
-
writeFileSync23(
|
|
15256
|
+
const skillDir = join32(baseDir, skill.filename);
|
|
15257
|
+
mkdirSync23(skillDir, { recursive: true });
|
|
15258
|
+
writeFileSync23(join32(skillDir, "SKILL.md"), buildSkillMd2(skill));
|
|
15126
15259
|
}
|
|
15127
15260
|
return skills.length;
|
|
15128
15261
|
}
|
|
15129
15262
|
async writeInstructionHint(hint, scope) {
|
|
15130
|
-
const filePath = scope === "project" ?
|
|
15263
|
+
const filePath = scope === "project" ? join32(process.cwd(), "GEMINI.md") : join32(homedir14(), ".gemini", "GEMINI.md");
|
|
15131
15264
|
writeHintToFile(filePath, hint);
|
|
15132
15265
|
}
|
|
15133
15266
|
async writeTeamInstructions(instructions, scope) {
|
|
15134
|
-
const filePath = scope === "project" ?
|
|
15267
|
+
const filePath = scope === "project" ? join32(process.cwd(), "GEMINI.md") : join32(homedir14(), ".gemini", "GEMINI.md");
|
|
15135
15268
|
writeTeamInstructionsToFile(filePath, instructions);
|
|
15136
15269
|
}
|
|
15137
15270
|
async writeAgentConfig(config, scope) {
|
|
15138
|
-
const settingsPath = scope === "project" ?
|
|
15271
|
+
const settingsPath = scope === "project" ? join32(process.cwd(), ".gemini", "settings.json") : join32(homedir14(), ".gemini", "settings.json");
|
|
15139
15272
|
let settings = {};
|
|
15140
|
-
if (
|
|
15273
|
+
if (existsSync39(settingsPath)) {
|
|
15141
15274
|
try {
|
|
15142
|
-
settings = JSON.parse(
|
|
15275
|
+
settings = JSON.parse(readFileSync29(settingsPath, "utf-8"));
|
|
15143
15276
|
} catch {}
|
|
15144
15277
|
}
|
|
15145
15278
|
if (config.modelPreference) {
|
|
@@ -15157,13 +15290,13 @@ class GeminiAdapter {
|
|
|
15157
15290
|
settings.tools = {};
|
|
15158
15291
|
settings.tools.exclude = config.permissionRules.deny;
|
|
15159
15292
|
}
|
|
15160
|
-
|
|
15293
|
+
mkdirSync23(join32(settingsPath, ".."), { recursive: true });
|
|
15161
15294
|
writeFileSync23(settingsPath, JSON.stringify(settings, null, 2));
|
|
15162
15295
|
}
|
|
15163
15296
|
async readUsageStats(lastSyncAt) {
|
|
15164
15297
|
try {
|
|
15165
|
-
const tmpDir =
|
|
15166
|
-
if (!
|
|
15298
|
+
const tmpDir = join32(homedir14(), ".gemini", "tmp");
|
|
15299
|
+
if (!existsSync39(tmpDir))
|
|
15167
15300
|
return null;
|
|
15168
15301
|
const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
|
|
15169
15302
|
let sessionCount = 0;
|
|
@@ -15172,27 +15305,27 @@ class GeminiAdapter {
|
|
|
15172
15305
|
const activeDays = new Set;
|
|
15173
15306
|
let projects;
|
|
15174
15307
|
try {
|
|
15175
|
-
projects =
|
|
15308
|
+
projects = readdirSync13(tmpDir, { withFileTypes: true });
|
|
15176
15309
|
} catch {
|
|
15177
15310
|
return null;
|
|
15178
15311
|
}
|
|
15179
15312
|
for (const project of projects) {
|
|
15180
15313
|
if (!project.isDirectory())
|
|
15181
15314
|
continue;
|
|
15182
|
-
const chatsDir =
|
|
15315
|
+
const chatsDir = join32(tmpDir, project.name, "chats");
|
|
15183
15316
|
let files;
|
|
15184
15317
|
try {
|
|
15185
|
-
files =
|
|
15318
|
+
files = readdirSync13(chatsDir, { withFileTypes: true });
|
|
15186
15319
|
} catch {
|
|
15187
15320
|
continue;
|
|
15188
15321
|
}
|
|
15189
15322
|
for (const file of files) {
|
|
15190
15323
|
if (!file.name.startsWith("session-") || !file.name.endsWith(".json"))
|
|
15191
15324
|
continue;
|
|
15192
|
-
const filePath =
|
|
15325
|
+
const filePath = join32(chatsDir, file.name);
|
|
15193
15326
|
let stat;
|
|
15194
15327
|
try {
|
|
15195
|
-
stat =
|
|
15328
|
+
stat = statSync7(filePath);
|
|
15196
15329
|
} catch {
|
|
15197
15330
|
continue;
|
|
15198
15331
|
}
|
|
@@ -15200,7 +15333,7 @@ class GeminiAdapter {
|
|
|
15200
15333
|
continue;
|
|
15201
15334
|
let session;
|
|
15202
15335
|
try {
|
|
15203
|
-
session = JSON.parse(
|
|
15336
|
+
session = JSON.parse(readFileSync29(filePath, "utf-8"));
|
|
15204
15337
|
} catch {
|
|
15205
15338
|
continue;
|
|
15206
15339
|
}
|
|
@@ -15240,31 +15373,25 @@ class GeminiAdapter {
|
|
|
15240
15373
|
return null;
|
|
15241
15374
|
}
|
|
15242
15375
|
}
|
|
15376
|
+
async removeSkills(skillFilenames, scope) {
|
|
15377
|
+
const skillsDir = scope === "project" ? join32(process.cwd(), ".gemini", "skills") : join32(homedir14(), ".gemini", "skills");
|
|
15378
|
+
removeMatchingSkillDirs(skillsDir, new Set(skillFilenames));
|
|
15379
|
+
}
|
|
15243
15380
|
async cleanup(scope, manifest) {
|
|
15244
15381
|
if (scope === "user") {
|
|
15245
|
-
removeRunworkMcpServers(
|
|
15246
|
-
}
|
|
15247
|
-
const skillsDir = scope === "project" ? join30(process.cwd(), ".gemini", "skills") : join30(homedir13(), ".gemini", "skills");
|
|
15248
|
-
if (existsSync36(skillsDir) && manifest?.skillFilenames.length) {
|
|
15249
|
-
const allowed = new Set(manifest.skillFilenames);
|
|
15250
|
-
for (const entry of readdirSync11(skillsDir)) {
|
|
15251
|
-
if (!allowed.has(entry))
|
|
15252
|
-
continue;
|
|
15253
|
-
try {
|
|
15254
|
-
rmSync10(join30(skillsDir, entry), { recursive: true, force: true });
|
|
15255
|
-
} catch {}
|
|
15256
|
-
}
|
|
15382
|
+
removeRunworkMcpServers(join32(homedir14(), ".gemini", "settings.json"), "mcpServers");
|
|
15257
15383
|
}
|
|
15258
|
-
|
|
15384
|
+
await this.removeSkills(manifest?.skillFilenames ?? [], scope);
|
|
15385
|
+
const instructionFile = scope === "project" ? join32(process.cwd(), "GEMINI.md") : join32(homedir14(), ".gemini", "GEMINI.md");
|
|
15259
15386
|
removeHintFromFile(instructionFile);
|
|
15260
15387
|
removeTeamInstructionsFromFile(instructionFile);
|
|
15261
15388
|
}
|
|
15262
15389
|
}
|
|
15263
15390
|
|
|
15264
15391
|
// src/agents/generic-adapter.ts
|
|
15265
|
-
import { existsSync as
|
|
15266
|
-
import { join as
|
|
15267
|
-
import { homedir as
|
|
15392
|
+
import { existsSync as existsSync40, mkdirSync as mkdirSync24, rmSync as rmSync12, writeFileSync as writeFileSync24 } from "fs";
|
|
15393
|
+
import { join as join33 } from "path";
|
|
15394
|
+
import { homedir as homedir15 } from "os";
|
|
15268
15395
|
class GenericAgentAdapter {
|
|
15269
15396
|
name;
|
|
15270
15397
|
slug;
|
|
@@ -15289,7 +15416,7 @@ class GenericAgentAdapter {
|
|
|
15289
15416
|
async writeMcpServers(servers, _scope) {
|
|
15290
15417
|
if (!this.def.mcpConfigPath)
|
|
15291
15418
|
return;
|
|
15292
|
-
const filePath =
|
|
15419
|
+
const filePath = join33(homedir15(), resolvePlatformString(this.def.mcpConfigPath) || "");
|
|
15293
15420
|
if (!filePath)
|
|
15294
15421
|
return;
|
|
15295
15422
|
const entries = {};
|
|
@@ -15313,16 +15440,16 @@ class GenericAgentAdapter {
|
|
|
15313
15440
|
return 0;
|
|
15314
15441
|
for (const skill of skills) {
|
|
15315
15442
|
if (skill.name !== skill.filename) {
|
|
15316
|
-
const oldDir =
|
|
15317
|
-
if (
|
|
15443
|
+
const oldDir = join33(baseDir, skill.name);
|
|
15444
|
+
if (existsSync40(oldDir)) {
|
|
15318
15445
|
try {
|
|
15319
|
-
|
|
15446
|
+
rmSync12(oldDir, { recursive: true, force: true });
|
|
15320
15447
|
} catch {}
|
|
15321
15448
|
}
|
|
15322
15449
|
}
|
|
15323
|
-
const skillDir =
|
|
15324
|
-
|
|
15325
|
-
writeFileSync24(
|
|
15450
|
+
const skillDir = join33(baseDir, skill.filename);
|
|
15451
|
+
mkdirSync24(skillDir, { recursive: true });
|
|
15452
|
+
writeFileSync24(join33(skillDir, "SKILL.md"), buildSkillMd2(skill));
|
|
15326
15453
|
}
|
|
15327
15454
|
return skills.length;
|
|
15328
15455
|
}
|
|
@@ -15348,31 +15475,25 @@ class GenericAgentAdapter {
|
|
|
15348
15475
|
return;
|
|
15349
15476
|
writeTeamInstructionsToFile(filePath, instructions);
|
|
15350
15477
|
}
|
|
15478
|
+
async removeSkills(skillFilenames, scope) {
|
|
15479
|
+
if (!this.def.skillsPaths || !skillFilenames.length)
|
|
15480
|
+
return;
|
|
15481
|
+
const pathTemplate = scope === "project" ? this.def.skillsPaths.project : this.def.skillsPaths.global;
|
|
15482
|
+
if (!pathTemplate)
|
|
15483
|
+
return;
|
|
15484
|
+
const baseDir = resolveToAbsolute(pathTemplate, scope === "project" ? "project" : "global");
|
|
15485
|
+
if (baseDir)
|
|
15486
|
+
removeMatchingSkillDirs(baseDir, new Set(skillFilenames));
|
|
15487
|
+
}
|
|
15351
15488
|
async cleanup(scope, manifest) {
|
|
15352
15489
|
if (this.def.mcpConfigPath && scope === "user") {
|
|
15353
15490
|
const resolved = resolvePlatformString(this.def.mcpConfigPath);
|
|
15354
15491
|
if (resolved) {
|
|
15355
|
-
const filePath =
|
|
15492
|
+
const filePath = join33(homedir15(), resolved);
|
|
15356
15493
|
removeRunworkMcpServers(filePath, this.def.mcpConfigKey || "mcpServers");
|
|
15357
15494
|
}
|
|
15358
15495
|
}
|
|
15359
|
-
|
|
15360
|
-
const pathTemplate = scope === "project" ? this.def.skillsPaths.project : this.def.skillsPaths.global;
|
|
15361
|
-
if (pathTemplate) {
|
|
15362
|
-
const baseDir = resolveToAbsolute(pathTemplate, scope === "project" ? "project" : "global");
|
|
15363
|
-
if (baseDir && existsSync37(baseDir)) {
|
|
15364
|
-
const allowed = new Set(manifest.skillFilenames);
|
|
15365
|
-
for (const entry of readdirSync12(baseDir)) {
|
|
15366
|
-
if (!allowed.has(entry))
|
|
15367
|
-
continue;
|
|
15368
|
-
const entryPath = join31(baseDir, entry);
|
|
15369
|
-
try {
|
|
15370
|
-
rmSync11(entryPath, { recursive: true, force: true });
|
|
15371
|
-
} catch {}
|
|
15372
|
-
}
|
|
15373
|
-
}
|
|
15374
|
-
}
|
|
15375
|
-
}
|
|
15496
|
+
await this.removeSkills(manifest?.skillFilenames ?? [], scope);
|
|
15376
15497
|
if (this.def.instructionFile) {
|
|
15377
15498
|
const pathTemplate = scope === "project" ? this.def.instructionFile.project : this.def.instructionFile.global;
|
|
15378
15499
|
if (pathTemplate) {
|
|
@@ -15448,27 +15569,26 @@ function insightLocalKey(teaches, slug) {
|
|
|
15448
15569
|
}
|
|
15449
15570
|
|
|
15450
15571
|
// src/reflect/insight-store.ts
|
|
15451
|
-
|
|
15452
|
-
import {
|
|
15453
|
-
import {
|
|
15572
|
+
init_atomic_json();
|
|
15573
|
+
import { existsSync as existsSync41, readFileSync as readFileSync30 } from "fs";
|
|
15574
|
+
import { join as join34 } from "path";
|
|
15575
|
+
import { homedir as homedir16 } from "os";
|
|
15454
15576
|
function storePath() {
|
|
15455
|
-
return
|
|
15577
|
+
return join34(homedir16(), ".runwork", "insights.json");
|
|
15456
15578
|
}
|
|
15457
15579
|
function readAll() {
|
|
15458
15580
|
const path2 = storePath();
|
|
15459
|
-
if (!
|
|
15581
|
+
if (!existsSync41(path2))
|
|
15460
15582
|
return {};
|
|
15461
15583
|
try {
|
|
15462
|
-
const parsed = JSON.parse(
|
|
15584
|
+
const parsed = JSON.parse(readFileSync30(path2, "utf-8"));
|
|
15463
15585
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
15464
15586
|
} catch {
|
|
15465
15587
|
return {};
|
|
15466
15588
|
}
|
|
15467
15589
|
}
|
|
15468
15590
|
function writeAll(store) {
|
|
15469
|
-
|
|
15470
|
-
mkdirSync24(join32(homedir15(), ".runwork"), { recursive: true });
|
|
15471
|
-
writeFileSync25(path2, JSON.stringify(store, null, 2));
|
|
15591
|
+
writeJsonAtomic(storePath(), store);
|
|
15472
15592
|
}
|
|
15473
15593
|
function notExpired(i, now) {
|
|
15474
15594
|
const t = new Date(i.expiresAt).getTime();
|
|
@@ -15488,37 +15608,43 @@ function loadInsights(opts = {}) {
|
|
|
15488
15608
|
const now = Date.now();
|
|
15489
15609
|
return Object.values(readAll()).filter((i) => notExpired(i, now) && (!opts.workspaceId || i.workspaceId === opts.workspaceId)).sort((a, b) => (b.createdAt ?? "").localeCompare(a.createdAt ?? ""));
|
|
15490
15610
|
}
|
|
15491
|
-
function getInsight(id) {
|
|
15611
|
+
function getInsight(id, opts = {}) {
|
|
15492
15612
|
const i = readAll()[id];
|
|
15493
|
-
|
|
15613
|
+
if (!i || !notExpired(i, Date.now()))
|
|
15614
|
+
return null;
|
|
15615
|
+
const allowed = (opts.workspaceIds ?? []).filter((w) => !!w);
|
|
15616
|
+
if (allowed.length && i.workspaceId && i.workspaceId !== "local" && !allowed.includes(i.workspaceId)) {
|
|
15617
|
+
return null;
|
|
15618
|
+
}
|
|
15619
|
+
return i;
|
|
15494
15620
|
}
|
|
15495
15621
|
|
|
15496
15622
|
// src/reflect/cadence.ts
|
|
15497
|
-
|
|
15498
|
-
import {
|
|
15499
|
-
import {
|
|
15623
|
+
init_atomic_json();
|
|
15624
|
+
import { existsSync as existsSync42, readFileSync as readFileSync31 } from "fs";
|
|
15625
|
+
import { join as join35 } from "path";
|
|
15626
|
+
import { homedir as homedir17 } from "os";
|
|
15500
15627
|
var DEFAULT_STATE = { enabled: false, lastReflectedAt: null };
|
|
15501
15628
|
var COOLDOWN_HOURS = 20;
|
|
15502
15629
|
var ACTIVE_SESSION_THRESHOLD = 10;
|
|
15503
15630
|
var MAX_INTERVAL_HOURS = 7 * 24;
|
|
15504
15631
|
var MIN_SESSIONS_FIRST_RUN = 3;
|
|
15505
15632
|
function statePath() {
|
|
15506
|
-
return
|
|
15633
|
+
return join35(homedir17(), ".runwork", "reflect-state.json");
|
|
15507
15634
|
}
|
|
15508
15635
|
function loadCadenceState() {
|
|
15509
15636
|
try {
|
|
15510
15637
|
const p = statePath();
|
|
15511
|
-
if (!
|
|
15638
|
+
if (!existsSync42(p))
|
|
15512
15639
|
return { ...DEFAULT_STATE };
|
|
15513
|
-
const parsed = JSON.parse(
|
|
15640
|
+
const parsed = JSON.parse(readFileSync31(p, "utf-8"));
|
|
15514
15641
|
return { ...DEFAULT_STATE, ...parsed && typeof parsed === "object" ? parsed : {} };
|
|
15515
15642
|
} catch {
|
|
15516
15643
|
return { ...DEFAULT_STATE };
|
|
15517
15644
|
}
|
|
15518
15645
|
}
|
|
15519
15646
|
function saveCadenceState(state) {
|
|
15520
|
-
|
|
15521
|
-
writeFileSync26(statePath(), JSON.stringify(state, null, 2));
|
|
15647
|
+
writeJsonAtomic(statePath(), state);
|
|
15522
15648
|
}
|
|
15523
15649
|
function recordReflection(now = new Date) {
|
|
15524
15650
|
saveCadenceState({ ...loadCadenceState(), lastReflectedAt: now.toISOString() });
|
|
@@ -15540,7 +15666,40 @@ function isReflectionDue(state, newSessionCount, now = new Date) {
|
|
|
15540
15666
|
return hoursSince >= MAX_INTERVAL_HOURS;
|
|
15541
15667
|
}
|
|
15542
15668
|
|
|
15669
|
+
// src/utils/workspace-state.ts
|
|
15670
|
+
init_atomic_json();
|
|
15671
|
+
import { join as join36 } from "path";
|
|
15672
|
+
import { homedir as homedir18 } from "os";
|
|
15673
|
+
function storePath2() {
|
|
15674
|
+
return join36(homedir18(), ".runwork", "workspaces.json");
|
|
15675
|
+
}
|
|
15676
|
+
function readFile() {
|
|
15677
|
+
const parsed = readJsonOrNull(storePath2());
|
|
15678
|
+
if (parsed && parsed.workspaces && typeof parsed.workspaces === "object")
|
|
15679
|
+
return parsed;
|
|
15680
|
+
return { workspaces: {} };
|
|
15681
|
+
}
|
|
15682
|
+
function loadWorkspaceRecord(workspaceId) {
|
|
15683
|
+
return readFile().workspaces[workspaceId] ?? {};
|
|
15684
|
+
}
|
|
15685
|
+
function updateWorkspaceRecord(workspaceId, patch) {
|
|
15686
|
+
if (!workspaceId)
|
|
15687
|
+
return;
|
|
15688
|
+
const file = readFile();
|
|
15689
|
+
file.workspaces[workspaceId] = { ...file.workspaces[workspaceId], ...patch };
|
|
15690
|
+
writeJsonAtomic(storePath2(), file);
|
|
15691
|
+
}
|
|
15692
|
+
function clearParkedState(workspaceId) {
|
|
15693
|
+
const file = readFile();
|
|
15694
|
+
const record = file.workspaces[workspaceId];
|
|
15695
|
+
if (!record?.parked)
|
|
15696
|
+
return;
|
|
15697
|
+
delete record.parked;
|
|
15698
|
+
writeJsonAtomic(storePath2(), file);
|
|
15699
|
+
}
|
|
15700
|
+
|
|
15543
15701
|
// src/commands/reflect.ts
|
|
15702
|
+
init_atomic_json();
|
|
15544
15703
|
init_colors();
|
|
15545
15704
|
var MAX_NAMES = 40;
|
|
15546
15705
|
function cap(names) {
|
|
@@ -15656,7 +15815,7 @@ function runAnalyst(binary, args, prompt, timeoutMs = ANALYST_TIMEOUT_MS) {
|
|
|
15656
15815
|
}
|
|
15657
15816
|
});
|
|
15658
15817
|
}
|
|
15659
|
-
function enrichInsights(evidence, userSeed) {
|
|
15818
|
+
function enrichInsights(evidence, userSeed, workspaceScope) {
|
|
15660
15819
|
const insights = evidence?.insights;
|
|
15661
15820
|
if (!Array.isArray(insights))
|
|
15662
15821
|
return [];
|
|
@@ -15670,7 +15829,7 @@ function enrichInsights(evidence, userSeed) {
|
|
|
15670
15829
|
if (!teaches || !slug)
|
|
15671
15830
|
continue;
|
|
15672
15831
|
items.push({
|
|
15673
|
-
id: computeInsightId(userSeed, insightLocalKey(teaches, slug)),
|
|
15832
|
+
id: computeInsightId(userSeed, `${workspaceScope}:${insightLocalKey(teaches, slug)}`),
|
|
15674
15833
|
source: "reflection",
|
|
15675
15834
|
teaches,
|
|
15676
15835
|
dimension: typeof r.dimension === "string" ? r.dimension : undefined,
|
|
@@ -15749,8 +15908,8 @@ var reflectCommand = new Command14("reflect").description("Weekly reflection: an
|
|
|
15749
15908
|
const save = (file, content) => {
|
|
15750
15909
|
if (!outDir)
|
|
15751
15910
|
return;
|
|
15752
|
-
|
|
15753
|
-
|
|
15911
|
+
mkdirSync25(outDir, { recursive: true });
|
|
15912
|
+
writeFileSync25(join37(outDir, file), content);
|
|
15754
15913
|
};
|
|
15755
15914
|
const adapters = await detectAgents();
|
|
15756
15915
|
const digests = [];
|
|
@@ -15808,14 +15967,23 @@ Skipping: ${cadence.enabled ? "not enough new work since the last reflection" :
|
|
|
15808
15967
|
return;
|
|
15809
15968
|
}
|
|
15810
15969
|
}
|
|
15970
|
+
let client;
|
|
15971
|
+
let workspaceId;
|
|
15972
|
+
try {
|
|
15973
|
+
client = new ApiClient(requireAuth());
|
|
15974
|
+
workspaceId = (await resolveWorkspace2(client, {})).workspaceId;
|
|
15975
|
+
} catch {
|
|
15976
|
+
try {
|
|
15977
|
+
workspaceId = requireAuth().defaultWorkspaceId;
|
|
15978
|
+
} catch {}
|
|
15979
|
+
}
|
|
15811
15980
|
{
|
|
15812
|
-
|
|
15981
|
+
const perWorkspace = workspaceId ? loadWorkspaceRecord(workspaceId).reflectionEnabled : undefined;
|
|
15982
|
+
let allowed = perWorkspace ?? loadCadenceState().workspaceReflectionEnabled ?? true;
|
|
15813
15983
|
try {
|
|
15814
|
-
|
|
15815
|
-
const { workspaceId } = await resolveWorkspace2(client, {});
|
|
15816
|
-
if (workspaceId) {
|
|
15984
|
+
if (client && workspaceId) {
|
|
15817
15985
|
allowed = (await client.getReflectionSetting(workspaceId)).reflectionEnabled;
|
|
15818
|
-
|
|
15986
|
+
updateWorkspaceRecord(workspaceId, { reflectionEnabled: allowed });
|
|
15819
15987
|
}
|
|
15820
15988
|
} catch {}
|
|
15821
15989
|
if (!allowed) {
|
|
@@ -15890,21 +16058,11 @@ Analyst failed: ${e instanceof Error ? e.message : String(e)}`));
|
|
|
15890
16058
|
return "local";
|
|
15891
16059
|
}
|
|
15892
16060
|
})();
|
|
15893
|
-
const items = evidence ? enrichInsights(evidence, userSeed) : [];
|
|
16061
|
+
const items = evidence ? enrichInsights(evidence, userSeed, workspaceId ?? "local") : [];
|
|
15894
16062
|
if (evidence)
|
|
15895
16063
|
save("evidence.json", JSON.stringify(evidence, null, 2));
|
|
15896
16064
|
save("insights.json", JSON.stringify(items, null, 2));
|
|
15897
16065
|
if (items.length > 0) {
|
|
15898
|
-
let client;
|
|
15899
|
-
let workspaceId;
|
|
15900
|
-
try {
|
|
15901
|
-
client = new ApiClient(requireAuth());
|
|
15902
|
-
workspaceId = (await resolveWorkspace2(client, {})).workspaceId;
|
|
15903
|
-
} catch {
|
|
15904
|
-
try {
|
|
15905
|
-
workspaceId = requireAuth().defaultWorkspaceId;
|
|
15906
|
-
} catch {}
|
|
15907
|
-
}
|
|
15908
16066
|
const nowIso = new Date().toISOString();
|
|
15909
16067
|
const expiresIso = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString();
|
|
15910
16068
|
saveInsights(items.map((it) => ({
|
|
@@ -15974,8 +16132,9 @@ reflectCommand.command("show [id]").description("Show reflection insights stored
|
|
|
15974
16132
|
try {
|
|
15975
16133
|
workspaceId = requireAuth().defaultWorkspaceId;
|
|
15976
16134
|
} catch {}
|
|
16135
|
+
const projectWorkspaceId = readJsonOrNull(join37(process.cwd(), ".runwork.json"))?.workspaceId;
|
|
15977
16136
|
if (id) {
|
|
15978
|
-
const insight = getInsight(id);
|
|
16137
|
+
const insight = getInsight(id, { workspaceIds: [projectWorkspaceId, workspaceId] });
|
|
15979
16138
|
if (!insight) {
|
|
15980
16139
|
if (wantJson) {
|
|
15981
16140
|
jsonOut({ error: "not-found", id });
|
|
@@ -16016,16 +16175,16 @@ import * as path3 from "node:path";
|
|
|
16016
16175
|
init_prompt();
|
|
16017
16176
|
|
|
16018
16177
|
// src/utils/data-input.ts
|
|
16019
|
-
import { readFileSync as
|
|
16178
|
+
import { readFileSync as readFileSync32, existsSync as existsSync43 } from "fs";
|
|
16020
16179
|
async function parseDataInput(dataFlag) {
|
|
16021
16180
|
if (dataFlag) {
|
|
16022
16181
|
if (dataFlag.startsWith("@")) {
|
|
16023
16182
|
const filePath = dataFlag.slice(1);
|
|
16024
|
-
if (!
|
|
16183
|
+
if (!existsSync43(filePath)) {
|
|
16025
16184
|
console.error(`File not found: ${filePath}`);
|
|
16026
16185
|
process.exit(1);
|
|
16027
16186
|
}
|
|
16028
|
-
const content =
|
|
16187
|
+
const content = readFileSync32(filePath, "utf-8");
|
|
16029
16188
|
return parseJson(content, filePath);
|
|
16030
16189
|
}
|
|
16031
16190
|
return parseJson(dataFlag, "--data");
|
|
@@ -16165,6 +16324,9 @@ function resolveExportTargets(entities, filter) {
|
|
|
16165
16324
|
}
|
|
16166
16325
|
return { targets };
|
|
16167
16326
|
}
|
|
16327
|
+
function classifyExportError(message) {
|
|
16328
|
+
return message.includes("not deployed to production") ? "not_deployed" : "error";
|
|
16329
|
+
}
|
|
16168
16330
|
function appSlugForPath(appName, appId) {
|
|
16169
16331
|
const slug = appName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
16170
16332
|
return slug || appId;
|
|
@@ -16454,22 +16616,49 @@ var exportCommand = new Command15("export").description("Export entity data to l
|
|
|
16454
16616
|
}
|
|
16455
16617
|
const results = [];
|
|
16456
16618
|
for (const target of targets) {
|
|
16457
|
-
|
|
16458
|
-
|
|
16459
|
-
|
|
16460
|
-
|
|
16461
|
-
|
|
16462
|
-
|
|
16463
|
-
|
|
16619
|
+
try {
|
|
16620
|
+
results.push(await exportOneApp(client, workspaceId, target, {
|
|
16621
|
+
format,
|
|
16622
|
+
entityName: entity,
|
|
16623
|
+
wait: opts.wait !== false,
|
|
16624
|
+
outputRoot: opts.output,
|
|
16625
|
+
quiet: useJson
|
|
16626
|
+
}));
|
|
16627
|
+
} catch (err) {
|
|
16628
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
16629
|
+
const kind = classifyExportError(message);
|
|
16630
|
+
const status = kind === "not_deployed" ? "skipped" : "failed";
|
|
16631
|
+
if (!useJson) {
|
|
16632
|
+
console.error(kind === "not_deployed" ? `${target.appName}: skipped (not deployed to production)` : `${target.appName}: failed: ${message}`);
|
|
16633
|
+
}
|
|
16634
|
+
results.push({
|
|
16635
|
+
appId: target.appId,
|
|
16636
|
+
appName: target.appName,
|
|
16637
|
+
jobId: "",
|
|
16638
|
+
status,
|
|
16639
|
+
records: 0,
|
|
16640
|
+
sizeBytes: 0,
|
|
16641
|
+
outputDir: null,
|
|
16642
|
+
files: [],
|
|
16643
|
+
error: message
|
|
16644
|
+
});
|
|
16645
|
+
}
|
|
16464
16646
|
}
|
|
16465
16647
|
if (useJson) {
|
|
16466
16648
|
jsonOut({ exports: results });
|
|
16467
16649
|
return;
|
|
16468
16650
|
}
|
|
16651
|
+
const exported = results.filter((r) => r.status === "completed");
|
|
16652
|
+
const skipped = results.filter((r) => r.status === "skipped");
|
|
16469
16653
|
const failed = results.filter((r) => r.status === "failed");
|
|
16470
|
-
|
|
16471
|
-
|
|
16472
|
-
|
|
16654
|
+
const summary = [`${exported.length} app(s) exported`];
|
|
16655
|
+
if (skipped.length > 0)
|
|
16656
|
+
summary.push(`${skipped.length} skipped (not deployed)`);
|
|
16657
|
+
if (failed.length > 0)
|
|
16658
|
+
summary.push(`${failed.length} failed`);
|
|
16659
|
+
console.log(`
|
|
16660
|
+
${summary.join(", ")}.`);
|
|
16661
|
+
if (failed.length > 0 || opts.app && skipped.length > 0) {
|
|
16473
16662
|
process.exit(1);
|
|
16474
16663
|
}
|
|
16475
16664
|
} catch (err) {
|
|
@@ -17010,8 +17199,8 @@ var endpointsCommand = new Command18("endpoints").alias("routes").description("M
|
|
|
17010
17199
|
init_store();
|
|
17011
17200
|
init_client();
|
|
17012
17201
|
import { Command as Command19 } from "commander";
|
|
17013
|
-
import { writeFileSync as
|
|
17014
|
-
import { basename as
|
|
17202
|
+
import { writeFileSync as writeFileSync27, readFileSync as readFileSync33 } from "fs";
|
|
17203
|
+
import { basename as basename3 } from "path";
|
|
17015
17204
|
init_prompt();
|
|
17016
17205
|
init_http();
|
|
17017
17206
|
function formatSize(bytes) {
|
|
@@ -17097,14 +17286,14 @@ var downloadCommand = new Command19("download").description("Download a file fro
|
|
|
17097
17286
|
const credentials = requireAuth();
|
|
17098
17287
|
const client = new ApiClient(credentials);
|
|
17099
17288
|
const { workspaceId } = await resolveWorkspace2(client, opts);
|
|
17100
|
-
const outputPath = output ||
|
|
17289
|
+
const outputPath = output || basename3(key);
|
|
17101
17290
|
try {
|
|
17102
17291
|
const { url } = await client.getPresignedUrl(workspaceId, bucket, { action: "read", key });
|
|
17103
17292
|
const response = await httpFetch(url);
|
|
17104
17293
|
if (!response.ok) {
|
|
17105
17294
|
throw new Error(`Download failed: ${response.status} ${response.statusText}`);
|
|
17106
17295
|
}
|
|
17107
|
-
|
|
17296
|
+
writeFileSync27(outputPath, Buffer.from(await response.arrayBuffer()));
|
|
17108
17297
|
if (useJson) {
|
|
17109
17298
|
jsonOut({ success: true, bucket, key, outputPath });
|
|
17110
17299
|
return;
|
|
@@ -17120,9 +17309,9 @@ var uploadCommand = new Command19("upload").description("Upload a local file to
|
|
|
17120
17309
|
const credentials = requireAuth();
|
|
17121
17310
|
const client = new ApiClient(credentials);
|
|
17122
17311
|
const { workspaceId } = await resolveWorkspace2(client, opts);
|
|
17123
|
-
const objectKey = key ||
|
|
17312
|
+
const objectKey = key || basename3(localPath);
|
|
17124
17313
|
try {
|
|
17125
|
-
const fileBuffer =
|
|
17314
|
+
const fileBuffer = readFileSync33(localPath);
|
|
17126
17315
|
const { url } = await client.getPresignedUrl(workspaceId, bucket, { action: "write", key: objectKey });
|
|
17127
17316
|
const response = await httpFetch(url, { method: "PUT", body: fileBuffer });
|
|
17128
17317
|
if (!response.ok) {
|
|
@@ -17618,21 +17807,21 @@ function truncateUrl(url, max) {
|
|
|
17618
17807
|
var mcpCommand = new Command23("mcp").description("Manage workspace MCP servers").addCommand(listCommand11).addCommand(addCommand).addCommand(removeCommand).addCommand(searchCommand3).addCommand(installCommand2);
|
|
17619
17808
|
|
|
17620
17809
|
// src/commands/setup.ts
|
|
17810
|
+
init_atomic_json();
|
|
17621
17811
|
init_store();
|
|
17622
17812
|
init_client();
|
|
17623
17813
|
import { Command as Command25 } from "commander";
|
|
17624
|
-
import {
|
|
17625
|
-
import {
|
|
17626
|
-
import { homedir as homedir19 } from "os";
|
|
17814
|
+
import { join as join41 } from "path";
|
|
17815
|
+
import { homedir as homedir20 } from "os";
|
|
17627
17816
|
init_prompt();
|
|
17628
17817
|
|
|
17629
17818
|
// src/commands/sync.ts
|
|
17630
17819
|
init_store();
|
|
17631
17820
|
init_client();
|
|
17632
17821
|
import { Command as Command24 } from "commander";
|
|
17633
|
-
import { readFileSync as
|
|
17634
|
-
import { join as
|
|
17635
|
-
import { homedir as
|
|
17822
|
+
import { readFileSync as readFileSync34, existsSync as existsSync45 } from "fs";
|
|
17823
|
+
import { join as join40 } from "path";
|
|
17824
|
+
import { homedir as homedir19 } from "os";
|
|
17636
17825
|
|
|
17637
17826
|
// src/commands/mcp-entries.ts
|
|
17638
17827
|
function buildExternalMcpEntries(mcpServers, resolvedCredentials) {
|
|
@@ -18610,6 +18799,9 @@ function computeSyncPlan(input) {
|
|
|
18610
18799
|
return plan;
|
|
18611
18800
|
}
|
|
18612
18801
|
|
|
18802
|
+
// src/commands/sync.ts
|
|
18803
|
+
init_atomic_json();
|
|
18804
|
+
|
|
18613
18805
|
// src/sync/conflict-ui.ts
|
|
18614
18806
|
init_prompt();
|
|
18615
18807
|
init_colors();
|
|
@@ -18796,8 +18988,20 @@ async function executeSyncPlan(plan, resolvedConflicts, ctx) {
|
|
|
18796
18988
|
}
|
|
18797
18989
|
}
|
|
18798
18990
|
for (const _action of plan.skips) {}
|
|
18799
|
-
|
|
18800
|
-
|
|
18991
|
+
const deletionSlugs = plan.deletions.map((action) => toSlug(action.name));
|
|
18992
|
+
if (deletionSlugs.length) {
|
|
18993
|
+
for (const adapter2 of ctx.adapters) {
|
|
18994
|
+
if (!adapter2.supportsSkills() || !adapter2.removeSkills)
|
|
18995
|
+
continue;
|
|
18996
|
+
for (const scope of ctx.scopes) {
|
|
18997
|
+
try {
|
|
18998
|
+
await adapter2.removeSkills(deletionSlugs, scope);
|
|
18999
|
+
} catch {}
|
|
19000
|
+
}
|
|
19001
|
+
}
|
|
19002
|
+
for (const action of plan.deletions) {
|
|
19003
|
+
vlog(` Deleted locally: ${action.name}`);
|
|
19004
|
+
}
|
|
18801
19005
|
}
|
|
18802
19006
|
return newHashes;
|
|
18803
19007
|
}
|
|
@@ -18890,10 +19094,10 @@ Tip: ${hint.title}`);
|
|
|
18890
19094
|
} catch {}
|
|
18891
19095
|
}
|
|
18892
19096
|
function loadSetupState(filePath) {
|
|
18893
|
-
if (!
|
|
19097
|
+
if (!existsSync45(filePath))
|
|
18894
19098
|
return null;
|
|
18895
19099
|
try {
|
|
18896
|
-
return JSON.parse(
|
|
19100
|
+
return JSON.parse(readFileSync34(filePath, "utf-8"));
|
|
18897
19101
|
} catch {
|
|
18898
19102
|
return null;
|
|
18899
19103
|
}
|
|
@@ -18914,15 +19118,15 @@ function readLocalSkills(state) {
|
|
|
18914
19118
|
if (!baseDir)
|
|
18915
19119
|
continue;
|
|
18916
19120
|
for (const skillName of state.skills) {
|
|
18917
|
-
const skillMdPath =
|
|
18918
|
-
if (
|
|
18919
|
-
results.push({ name: skillName, content:
|
|
19121
|
+
const skillMdPath = join40(baseDir, skillName, "SKILL.md");
|
|
19122
|
+
if (existsSync45(skillMdPath)) {
|
|
19123
|
+
results.push({ name: skillName, content: readFileSync34(skillMdPath, "utf-8") });
|
|
18920
19124
|
continue;
|
|
18921
19125
|
}
|
|
18922
19126
|
const filename = skillName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
18923
|
-
const flatPath =
|
|
18924
|
-
if (
|
|
18925
|
-
results.push({ name: skillName, content:
|
|
19127
|
+
const flatPath = join40(baseDir, `${filename}.md`);
|
|
19128
|
+
if (existsSync45(flatPath)) {
|
|
19129
|
+
results.push({ name: skillName, content: readFileSync34(flatPath, "utf-8") });
|
|
18926
19130
|
}
|
|
18927
19131
|
}
|
|
18928
19132
|
if (results.length > 0)
|
|
@@ -19126,9 +19330,9 @@ async function syncFromState(state, statePath2, credentials, opts) {
|
|
|
19126
19330
|
persona: state.persona
|
|
19127
19331
|
});
|
|
19128
19332
|
let projectAppSkillFilter = null;
|
|
19129
|
-
if (
|
|
19333
|
+
if (existsSync45(".runwork.json")) {
|
|
19130
19334
|
try {
|
|
19131
|
-
const config = JSON.parse(
|
|
19335
|
+
const config = JSON.parse(readFileSync34(".runwork.json", "utf-8"));
|
|
19132
19336
|
if (config.appName) {
|
|
19133
19337
|
projectAppSkillFilter = config.appName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
19134
19338
|
}
|
|
@@ -19365,7 +19569,7 @@ async function syncFromState(state, statePath2, credentials, opts) {
|
|
|
19365
19569
|
}
|
|
19366
19570
|
for (const adapter2 of adapters) {
|
|
19367
19571
|
if (adapter2 instanceof CodexAdapter) {
|
|
19368
|
-
const runworkDir =
|
|
19572
|
+
const runworkDir = join40(homedir19(), ".runwork");
|
|
19369
19573
|
const result = adapter2.registerDesktopWorkspace(runworkDir, "Runwork");
|
|
19370
19574
|
if (result === "written") {
|
|
19371
19575
|
vlog(` [${adapter2.name}] Registered workspace in Codex desktop app`);
|
|
@@ -19440,7 +19644,7 @@ async function syncFromState(state, statePath2, credentials, opts) {
|
|
|
19440
19644
|
delete mergedHashes[del.name];
|
|
19441
19645
|
}
|
|
19442
19646
|
state.skillHashes = mergedHashes;
|
|
19443
|
-
|
|
19647
|
+
writeJsonAtomic(statePath2, state);
|
|
19444
19648
|
try {
|
|
19445
19649
|
const telemetryNow = new Date().toISOString();
|
|
19446
19650
|
const telemetry = await collectTelemetryEvents({
|
|
@@ -19460,7 +19664,13 @@ async function syncFromState(state, statePath2, credentials, opts) {
|
|
|
19460
19664
|
if (telemetry.healthReported) {
|
|
19461
19665
|
state.lastHealthReportAt = new Date().toISOString();
|
|
19462
19666
|
}
|
|
19463
|
-
|
|
19667
|
+
writeJsonAtomic(statePath2, state);
|
|
19668
|
+
updateWorkspaceRecord(state.workspaceId, {
|
|
19669
|
+
workspaceName: state.workspaceName || undefined,
|
|
19670
|
+
workspaceSlug: state.workspaceSlug,
|
|
19671
|
+
lastTelemetryAt: state.lastTelemetryAt,
|
|
19672
|
+
lastHealthReportAt: state.lastHealthReportAt
|
|
19673
|
+
});
|
|
19464
19674
|
} catch {}
|
|
19465
19675
|
const failedNote = summary.adaptersFailed > 0 ? ` (${summary.adaptersFailed} failed)` : "";
|
|
19466
19676
|
if (isVerbose()) {
|
|
@@ -19498,8 +19708,8 @@ var syncCommand = new Command24("sync").description("Sync skills bidirectionally
|
|
|
19498
19708
|
verbose: !!opts.verbose,
|
|
19499
19709
|
redetect: !!opts.redetect
|
|
19500
19710
|
};
|
|
19501
|
-
const projectStatePath =
|
|
19502
|
-
const userStatePath =
|
|
19711
|
+
const projectStatePath = join40(process.cwd(), ".runwork", "setup.json");
|
|
19712
|
+
const userStatePath = join40(homedir19(), ".runwork", "setup.json");
|
|
19503
19713
|
const projectState = loadSetupState(projectStatePath);
|
|
19504
19714
|
const userState = loadSetupState(userStatePath);
|
|
19505
19715
|
if (!projectState && !userState) {
|
|
@@ -19519,25 +19729,6 @@ Sync complete.`);
|
|
|
19519
19729
|
}
|
|
19520
19730
|
});
|
|
19521
19731
|
|
|
19522
|
-
// src/utils/setup-state.ts
|
|
19523
|
-
import { existsSync as existsSync43, readFileSync as readFileSync34 } from "fs";
|
|
19524
|
-
import { join as join38 } from "path";
|
|
19525
|
-
import { homedir as homedir18 } from "os";
|
|
19526
|
-
function loadSetupState2() {
|
|
19527
|
-
const projectPath = join38(process.cwd(), ".runwork", "setup.json");
|
|
19528
|
-
const userPath = join38(homedir18(), ".runwork", "setup.json");
|
|
19529
|
-
for (const p of [projectPath, userPath]) {
|
|
19530
|
-
if (existsSync43(p)) {
|
|
19531
|
-
try {
|
|
19532
|
-
return JSON.parse(readFileSync34(p, "utf-8"));
|
|
19533
|
-
} catch {
|
|
19534
|
-
continue;
|
|
19535
|
-
}
|
|
19536
|
-
}
|
|
19537
|
-
}
|
|
19538
|
-
return null;
|
|
19539
|
-
}
|
|
19540
|
-
|
|
19541
19732
|
// src/commands/setup.ts
|
|
19542
19733
|
var PERSONA_LABELS = {
|
|
19543
19734
|
1: "everyday",
|
|
@@ -19553,7 +19744,7 @@ function resolvePersona(flag, existing) {
|
|
|
19553
19744
|
}
|
|
19554
19745
|
return existing;
|
|
19555
19746
|
}
|
|
19556
|
-
async function
|
|
19747
|
+
async function resolveSetupWorkspace(client, opts) {
|
|
19557
19748
|
const resolved = await resolveWorkspace2(client, opts);
|
|
19558
19749
|
const { workspaceId } = resolved;
|
|
19559
19750
|
let allWorkspaces = [];
|
|
@@ -19565,29 +19756,89 @@ async function resolveAndPersistWorkspace(client, opts) {
|
|
|
19565
19756
|
console.error(`Workspace "${opts.workspace}" not found or not accessible.`);
|
|
19566
19757
|
process.exit(1);
|
|
19567
19758
|
}
|
|
19568
|
-
|
|
19569
|
-
|
|
19759
|
+
return {
|
|
19760
|
+
workspaceId,
|
|
19761
|
+
workspaceName: resolved.workspaceName || workspaceMeta?.name || "",
|
|
19762
|
+
workspaceSlug: workspaceMeta?.slug
|
|
19763
|
+
};
|
|
19764
|
+
}
|
|
19765
|
+
function persistDefaultWorkspace(workspaceId, workspaceName) {
|
|
19570
19766
|
const freshCreds = getCredentials();
|
|
19571
|
-
if (freshCreds)
|
|
19572
|
-
|
|
19573
|
-
|
|
19574
|
-
|
|
19575
|
-
|
|
19576
|
-
|
|
19767
|
+
if (!freshCreds)
|
|
19768
|
+
return;
|
|
19769
|
+
saveCredentials({
|
|
19770
|
+
...freshCreds,
|
|
19771
|
+
defaultWorkspaceId: workspaceId,
|
|
19772
|
+
defaultWorkspaceName: workspaceName
|
|
19773
|
+
});
|
|
19774
|
+
}
|
|
19775
|
+
function toSkillFilename(name) {
|
|
19776
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
19777
|
+
}
|
|
19778
|
+
function loadSetupStateForScope(scope) {
|
|
19779
|
+
const path4 = scope === "project" ? join41(process.cwd(), ".runwork", "setup.json") : join41(homedir20(), ".runwork", "setup.json");
|
|
19780
|
+
return readJsonOrNull(path4);
|
|
19781
|
+
}
|
|
19782
|
+
async function parkAndTeardownWorkspace(previous, scopes) {
|
|
19783
|
+
const cleanupScopes = scopes ?? (previous.scope === "both" ? ["project", "user"] : [previous.scope ?? "user"]);
|
|
19784
|
+
const skillFilenames = previous.skillFilenames ?? (previous.skills ?? []).map(toSkillFilename);
|
|
19785
|
+
const patch = {
|
|
19786
|
+
parked: {
|
|
19787
|
+
skillFilenames,
|
|
19788
|
+
mcpServers: previous.mcpServers ?? [],
|
|
19789
|
+
agentDefaults: previous.agentDefaults,
|
|
19790
|
+
agentDefaultsVersion: previous.agentDefaultsVersion,
|
|
19791
|
+
parkedAt: new Date().toISOString()
|
|
19792
|
+
}
|
|
19793
|
+
};
|
|
19794
|
+
if (previous.workspaceName)
|
|
19795
|
+
patch.workspaceName = previous.workspaceName;
|
|
19796
|
+
if (previous.workspaceSlug)
|
|
19797
|
+
patch.workspaceSlug = previous.workspaceSlug;
|
|
19798
|
+
if (previous.lastTelemetryAt)
|
|
19799
|
+
patch.lastTelemetryAt = previous.lastTelemetryAt;
|
|
19800
|
+
if (previous.lastHealthReportAt)
|
|
19801
|
+
patch.lastHealthReportAt = previous.lastHealthReportAt;
|
|
19802
|
+
updateWorkspaceRecord(previous.workspaceId, patch);
|
|
19803
|
+
const manifest = {
|
|
19804
|
+
skillFilenames,
|
|
19805
|
+
mcpServerNames: previous.mcpServers ?? []
|
|
19806
|
+
};
|
|
19807
|
+
for (const slug of previous.configuredAgents ?? []) {
|
|
19808
|
+
const adapter2 = getAdapterBySlug(slug);
|
|
19809
|
+
if (!adapter2?.cleanup)
|
|
19810
|
+
continue;
|
|
19811
|
+
for (const scope of cleanupScopes) {
|
|
19812
|
+
try {
|
|
19813
|
+
await adapter2.cleanup(scope, manifest);
|
|
19814
|
+
console.log(` [${adapter2.name}] Previous workspace removed (${scope})`);
|
|
19815
|
+
} catch (err) {
|
|
19816
|
+
console.warn(` [${adapter2.name}] Cleanup failed (${scope}): ${err instanceof Error ? err.message : err}`);
|
|
19817
|
+
}
|
|
19818
|
+
}
|
|
19819
|
+
}
|
|
19820
|
+
}
|
|
19821
|
+
function resolveTelemetryCursor(carry, targetRecord, now = new Date) {
|
|
19822
|
+
if (carry?.lastTelemetryAt)
|
|
19823
|
+
return carry.lastTelemetryAt;
|
|
19824
|
+
const watermark = targetRecord.lastTelemetryAt;
|
|
19825
|
+
const parkedAt = targetRecord.parked?.parkedAt;
|
|
19826
|
+
if (watermark && parkedAt) {
|
|
19827
|
+
return Date.parse(parkedAt) > Date.parse(watermark) ? parkedAt : watermark;
|
|
19577
19828
|
}
|
|
19578
|
-
return
|
|
19829
|
+
return parkedAt ?? watermark ?? now.toISOString();
|
|
19579
19830
|
}
|
|
19580
|
-
|
|
19831
|
+
async function runSetup(opts) {
|
|
19581
19832
|
const credentials = requireAuth();
|
|
19582
19833
|
const client = new ApiClient(credentials);
|
|
19583
|
-
const { workspaceId, workspaceName, workspaceSlug } = await
|
|
19834
|
+
const { workspaceId, workspaceName, workspaceSlug } = await resolveSetupWorkspace(client, opts);
|
|
19584
19835
|
console.log(`
|
|
19585
19836
|
Workspace: ${workspaceName || workspaceId}
|
|
19586
19837
|
`);
|
|
19587
19838
|
let agents = await detectAgents();
|
|
19588
19839
|
if (agents.length === 0) {
|
|
19589
19840
|
printNoAgentsMessage();
|
|
19590
|
-
process.exit(
|
|
19841
|
+
process.exit(1);
|
|
19591
19842
|
}
|
|
19592
19843
|
if (opts.agent) {
|
|
19593
19844
|
agents = agents.filter((a) => a.slug === opts.agent);
|
|
@@ -19619,8 +19870,8 @@ Configuring all agents (--yes).
|
|
|
19619
19870
|
}
|
|
19620
19871
|
agents = kept;
|
|
19621
19872
|
if (agents.length === 0) {
|
|
19622
|
-
console.log("No agents selected.");
|
|
19623
|
-
|
|
19873
|
+
console.log("No agents selected. Nothing was changed.");
|
|
19874
|
+
process.exit(1);
|
|
19624
19875
|
}
|
|
19625
19876
|
}
|
|
19626
19877
|
}
|
|
@@ -19634,6 +19885,37 @@ Configuring all agents (--yes).
|
|
|
19634
19885
|
const chosen = await promptSelect("Configure for:", scopeChoices);
|
|
19635
19886
|
scope = chosen.value;
|
|
19636
19887
|
}
|
|
19888
|
+
const scopes = scope === "both" ? ["project", "user"] : [scope];
|
|
19889
|
+
const outgoing = new Map;
|
|
19890
|
+
for (const s of scopes) {
|
|
19891
|
+
const prior = loadSetupStateForScope(s);
|
|
19892
|
+
if (prior?.workspaceId && prior.workspaceId !== workspaceId) {
|
|
19893
|
+
outgoing.set(s, prior);
|
|
19894
|
+
}
|
|
19895
|
+
}
|
|
19896
|
+
const previous = loadSetupStateForScope(scopes.includes("user") ? "user" : "project");
|
|
19897
|
+
const isSwitch = outgoing.size > 0;
|
|
19898
|
+
if (isSwitch && !opts.dryRun) {
|
|
19899
|
+
const names = [...new Set([...outgoing.values()].map((p) => p.workspaceName || p.workspaceId))];
|
|
19900
|
+
console.log(`Switching workspace: ${names.join(", ")} -> ${workspaceName || workspaceId}`);
|
|
19901
|
+
console.log(`Removing the previous workspace's skills and connections from your AI tools...
|
|
19902
|
+
`);
|
|
19903
|
+
for (const [s, prior] of outgoing) {
|
|
19904
|
+
await parkAndTeardownWorkspace(prior, [s]);
|
|
19905
|
+
}
|
|
19906
|
+
const trashed = currentTrashBatch();
|
|
19907
|
+
if (trashed) {
|
|
19908
|
+
console.log(` A copy of everything removed is kept for ${TRASH_RETENTION_DAYS} days in:`);
|
|
19909
|
+
console.log(` ${trashed}`);
|
|
19910
|
+
}
|
|
19911
|
+
console.log("");
|
|
19912
|
+
} else if (isSwitch && opts.dryRun) {
|
|
19913
|
+
const names = [...new Set([...outgoing.values()].map((p) => p.workspaceName || p.workspaceId))];
|
|
19914
|
+
console.log(`[Dry run] Would switch workspace: ${names.join(", ")} -> ${workspaceName || workspaceId} (teardown + re-setup).`);
|
|
19915
|
+
}
|
|
19916
|
+
const carry = !isSwitch && previous?.workspaceId === workspaceId ? previous : undefined;
|
|
19917
|
+
const targetRecord = loadWorkspaceRecord(workspaceId);
|
|
19918
|
+
const restored = carry ? undefined : targetRecord.parked;
|
|
19637
19919
|
const state = {
|
|
19638
19920
|
workspaceId,
|
|
19639
19921
|
workspaceName: workspaceName || "",
|
|
@@ -19643,19 +19925,17 @@ Configuring all agents (--yes).
|
|
|
19643
19925
|
lastSyncAt: "",
|
|
19644
19926
|
mcpServers: [],
|
|
19645
19927
|
skills: [],
|
|
19646
|
-
skillHashes: {},
|
|
19928
|
+
skillHashes: carry?.skillHashes ?? {},
|
|
19929
|
+
agentDefaults: carry?.agentDefaults ?? restored?.agentDefaults,
|
|
19930
|
+
agentDefaultsVersion: carry?.agentDefaultsVersion ?? restored?.agentDefaultsVersion,
|
|
19931
|
+
lastTelemetryAt: resolveTelemetryCursor(carry, targetRecord),
|
|
19932
|
+
lastHealthReportAt: carry?.lastHealthReportAt ?? targetRecord.lastHealthReportAt,
|
|
19647
19933
|
lastDetectedAt: new Date().toISOString(),
|
|
19648
|
-
persona: resolvePersona(opts.persona,
|
|
19934
|
+
persona: resolvePersona(opts.persona, previous?.persona)
|
|
19649
19935
|
};
|
|
19650
|
-
const scopes = scope === "both" ? ["project", "user"] : [scope];
|
|
19651
|
-
for (const s of scopes) {
|
|
19652
|
-
const dir = s === "project" ? ".runwork" : join39(homedir19(), ".runwork");
|
|
19653
|
-
mkdirSync28(dir, { recursive: true });
|
|
19654
|
-
writeFileSync31(join39(dir, "setup.json"), JSON.stringify(state, null, 2));
|
|
19655
|
-
}
|
|
19656
19936
|
if (opts.dryRun) {
|
|
19657
19937
|
console.log(`
|
|
19658
|
-
[Dry run]
|
|
19938
|
+
[Dry run] Nothing written. Would save setup state and sync:
|
|
19659
19939
|
`);
|
|
19660
19940
|
console.log(` Agents: ${agents.map((a) => a.name).join(", ")}`);
|
|
19661
19941
|
console.log(` Scope: ${scope}`);
|
|
@@ -19663,11 +19943,18 @@ Configuring all agents (--yes).
|
|
|
19663
19943
|
Re-run without --dry-run to sync workspace data.`);
|
|
19664
19944
|
return;
|
|
19665
19945
|
}
|
|
19946
|
+
persistDefaultWorkspace(workspaceId, workspaceName);
|
|
19947
|
+
for (const s of scopes) {
|
|
19948
|
+
const dir = s === "project" ? ".runwork" : join41(homedir20(), ".runwork");
|
|
19949
|
+
writeJsonAtomic(join41(dir, "setup.json"), state);
|
|
19950
|
+
}
|
|
19951
|
+
if (restored)
|
|
19952
|
+
clearParkedState(workspaceId);
|
|
19666
19953
|
console.log(`
|
|
19667
19954
|
Syncing workspace data...
|
|
19668
19955
|
`);
|
|
19669
19956
|
for (const s of scopes) {
|
|
19670
|
-
const statePath2 = s === "project" ?
|
|
19957
|
+
const statePath2 = s === "project" ? join41(process.cwd(), ".runwork", "setup.json") : join41(homedir20(), ".runwork", "setup.json");
|
|
19671
19958
|
await syncFromState(state, statePath2, credentials, {
|
|
19672
19959
|
dryRun: false,
|
|
19673
19960
|
pullOnly: true,
|
|
@@ -19678,25 +19965,94 @@ Syncing workspace data...
|
|
|
19678
19965
|
}
|
|
19679
19966
|
console.log("\nSetup complete. Run `runwork sync` anytime to refresh.");
|
|
19680
19967
|
await printAdoptionHint(credentials, workspaceId);
|
|
19968
|
+
}
|
|
19969
|
+
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) => runSetup(opts));
|
|
19970
|
+
|
|
19971
|
+
// src/commands/workspace.ts
|
|
19972
|
+
init_store();
|
|
19973
|
+
init_client();
|
|
19974
|
+
import { Command as Command26 } from "commander";
|
|
19975
|
+
|
|
19976
|
+
// src/utils/setup-state.ts
|
|
19977
|
+
import { existsSync as existsSync46, readFileSync as readFileSync35 } from "fs";
|
|
19978
|
+
import { join as join42 } from "path";
|
|
19979
|
+
import { homedir as homedir21 } from "os";
|
|
19980
|
+
function loadSetupState2() {
|
|
19981
|
+
const projectPath = join42(process.cwd(), ".runwork", "setup.json");
|
|
19982
|
+
const userPath = join42(homedir21(), ".runwork", "setup.json");
|
|
19983
|
+
for (const p of [projectPath, userPath]) {
|
|
19984
|
+
if (existsSync46(p)) {
|
|
19985
|
+
try {
|
|
19986
|
+
return JSON.parse(readFileSync35(p, "utf-8"));
|
|
19987
|
+
} catch {
|
|
19988
|
+
continue;
|
|
19989
|
+
}
|
|
19990
|
+
}
|
|
19991
|
+
}
|
|
19992
|
+
return null;
|
|
19993
|
+
}
|
|
19994
|
+
|
|
19995
|
+
// src/commands/workspace.ts
|
|
19996
|
+
var workspaceCommand = new Command26("workspace").description("Inspect or switch the workspace this machine is set up for");
|
|
19997
|
+
workspaceCommand.command("list").description("List workspaces you belong to (the active one is marked)").action(async (_opts, command) => {
|
|
19998
|
+
const json = command.optsWithGlobals().json === true;
|
|
19999
|
+
const credentials = requireAuth();
|
|
20000
|
+
const client = new ApiClient(credentials);
|
|
20001
|
+
const workspaces = await client.listWorkspaces();
|
|
20002
|
+
const activeId = loadSetupState2()?.workspaceId ?? credentials.defaultWorkspaceId;
|
|
20003
|
+
if (json) {
|
|
20004
|
+
jsonOut({
|
|
20005
|
+
workspaces: workspaces.map((w) => ({ id: w.id, name: w.name, slug: w.slug, active: w.id === activeId }))
|
|
20006
|
+
});
|
|
20007
|
+
return;
|
|
20008
|
+
}
|
|
20009
|
+
if (workspaces.length === 0) {
|
|
20010
|
+
console.log("No workspaces found for this account.");
|
|
20011
|
+
return;
|
|
20012
|
+
}
|
|
20013
|
+
for (const w of workspaces) {
|
|
20014
|
+
const marker = w.id === activeId ? "*" : " ";
|
|
20015
|
+
console.log(` ${marker} ${w.name} (${w.id})`);
|
|
20016
|
+
}
|
|
20017
|
+
console.log("\n* = active on this machine. Switch with `runwork workspace switch <name-or-id>`.");
|
|
20018
|
+
});
|
|
20019
|
+
workspaceCommand.command("current").description("Show the workspace this machine is currently set up for").action(async (_opts, command) => {
|
|
20020
|
+
const json = command.optsWithGlobals().json === true;
|
|
20021
|
+
const state = loadSetupState2();
|
|
20022
|
+
const credentials = requireAuth();
|
|
20023
|
+
const workspaceId = state?.workspaceId ?? credentials.defaultWorkspaceId ?? null;
|
|
20024
|
+
const workspaceName = state?.workspaceName || credentials.defaultWorkspaceName || null;
|
|
20025
|
+
if (json) {
|
|
20026
|
+
jsonOut({ workspaceId, workspaceName, source: state?.workspaceId ? "setup" : "credentials" });
|
|
20027
|
+
return;
|
|
20028
|
+
}
|
|
20029
|
+
if (!workspaceId) {
|
|
20030
|
+
console.log("This machine is not set up for a workspace yet. Run `runwork setup`.");
|
|
20031
|
+
return;
|
|
20032
|
+
}
|
|
20033
|
+
console.log(`${workspaceName || workspaceId} (${workspaceId})`);
|
|
20034
|
+
});
|
|
20035
|
+
workspaceCommand.command("switch <name-or-id>").description("Re-setup this machine for another workspace (removes the current workspace's skills and connections from your AI tools, then sets up the new one)").option("--persona <level>", "Technical-level persona for agent instructions (1=everyday, 2=curious, 3=engineer)").action(async (target, opts) => {
|
|
20036
|
+
await runSetup({ workspace: target, yes: true, persona: opts.persona });
|
|
19681
20037
|
});
|
|
19682
20038
|
|
|
19683
20039
|
// src/commands/build-plugin.ts
|
|
19684
20040
|
init_store();
|
|
19685
20041
|
init_client();
|
|
19686
|
-
import { Command as
|
|
19687
|
-
import { existsSync as
|
|
19688
|
-
import { resolve as resolve3, join as
|
|
19689
|
-
import { homedir as
|
|
20042
|
+
import { Command as Command27 } from "commander";
|
|
20043
|
+
import { existsSync as existsSync47, readFileSync as readFileSync36 } from "fs";
|
|
20044
|
+
import { resolve as resolve3, join as join43 } from "path";
|
|
20045
|
+
import { homedir as homedir22 } from "os";
|
|
19690
20046
|
function loadSetupState3(filePath) {
|
|
19691
|
-
if (!
|
|
20047
|
+
if (!existsSync47(filePath))
|
|
19692
20048
|
return null;
|
|
19693
20049
|
try {
|
|
19694
|
-
return JSON.parse(
|
|
20050
|
+
return JSON.parse(readFileSync36(filePath, "utf-8"));
|
|
19695
20051
|
} catch {
|
|
19696
20052
|
return null;
|
|
19697
20053
|
}
|
|
19698
20054
|
}
|
|
19699
|
-
var buildPluginCommand = new
|
|
20055
|
+
var buildPluginCommand = new Command27("build-plugin").description("Build an installable plugin archive for an agent (e.g. Claude Desktop)").requiredOption("--agent <slug>", "Target agent slug (e.g. claude-desktop)").option("--output <path>", "Output zip path", "runwork-plugin.zip").action(async (opts) => {
|
|
19700
20056
|
const adapter2 = getAdapterBySlug(opts.agent);
|
|
19701
20057
|
if (!adapter2) {
|
|
19702
20058
|
console.error(`Unknown agent: ${opts.agent}`);
|
|
@@ -19707,8 +20063,8 @@ var buildPluginCommand = new Command26("build-plugin").description("Build an ins
|
|
|
19707
20063
|
process.exit(1);
|
|
19708
20064
|
}
|
|
19709
20065
|
const credentials = requireAuth();
|
|
19710
|
-
const projectStatePath =
|
|
19711
|
-
const userStatePath =
|
|
20066
|
+
const projectStatePath = join43(process.cwd(), ".runwork", "setup.json");
|
|
20067
|
+
const userStatePath = join43(homedir22(), ".runwork", "setup.json");
|
|
19712
20068
|
const state = loadSetupState3(projectStatePath) ?? loadSetupState3(userStatePath);
|
|
19713
20069
|
if (!state) {
|
|
19714
20070
|
console.error("No setup state found. Run `runwork setup` first.");
|
|
@@ -19797,23 +20153,23 @@ var buildPluginCommand = new Command26("build-plugin").description("Build an ins
|
|
|
19797
20153
|
});
|
|
19798
20154
|
|
|
19799
20155
|
// src/commands/uninstall.ts
|
|
19800
|
-
import { Command as
|
|
19801
|
-
import { existsSync as
|
|
19802
|
-
import { join as
|
|
19803
|
-
import { homedir as
|
|
20156
|
+
import { Command as Command28 } from "commander";
|
|
20157
|
+
import { existsSync as existsSync48, readFileSync as readFileSync37, rmSync as rmSync13, unlinkSync as unlinkSync7 } from "fs";
|
|
20158
|
+
import { join as join44 } from "path";
|
|
20159
|
+
import { homedir as homedir23 } from "os";
|
|
19804
20160
|
init_prompt();
|
|
19805
20161
|
function loadSetupState4(filePath) {
|
|
19806
|
-
if (!
|
|
20162
|
+
if (!existsSync48(filePath))
|
|
19807
20163
|
return null;
|
|
19808
20164
|
try {
|
|
19809
|
-
return JSON.parse(
|
|
20165
|
+
return JSON.parse(readFileSync37(filePath, "utf-8"));
|
|
19810
20166
|
} catch {
|
|
19811
20167
|
return null;
|
|
19812
20168
|
}
|
|
19813
20169
|
}
|
|
19814
|
-
var uninstallCommand = new
|
|
19815
|
-
const projectStatePath =
|
|
19816
|
-
const userStatePath =
|
|
20170
|
+
var uninstallCommand = new Command28("uninstall").description("Remove all Runwork configuration from local agents (MCP servers, skills, instructions)").option("-y, --yes", "Skip confirmation prompt").option("--keep-auth", "Keep authentication credentials (only remove agent configs)").action(async (opts) => {
|
|
20171
|
+
const projectStatePath = join44(process.cwd(), ".runwork", "setup.json");
|
|
20172
|
+
const userStatePath = join44(homedir23(), ".runwork", "setup.json");
|
|
19817
20173
|
const projectState = loadSetupState4(projectStatePath);
|
|
19818
20174
|
const userState = loadSetupState4(userStatePath);
|
|
19819
20175
|
if (!projectState && !userState) {
|
|
@@ -19893,10 +20249,10 @@ This will remove all Runwork configuration from your local agents:
|
|
|
19893
20249
|
}
|
|
19894
20250
|
}
|
|
19895
20251
|
}
|
|
19896
|
-
const stateDir = label === "project" ?
|
|
20252
|
+
const stateDir = label === "project" ? join44(process.cwd(), ".runwork") : join44(homedir23(), ".runwork");
|
|
19897
20253
|
if (opts.keepAuth && label === "user") {
|
|
19898
|
-
const setupFile =
|
|
19899
|
-
if (
|
|
20254
|
+
const setupFile = join44(stateDir, "setup.json");
|
|
20255
|
+
if (existsSync48(setupFile)) {
|
|
19900
20256
|
try {
|
|
19901
20257
|
unlinkSync7(setupFile);
|
|
19902
20258
|
console.log(` Removed ${setupFile} (kept credentials)`);
|
|
@@ -19905,9 +20261,9 @@ This will remove all Runwork configuration from your local agents:
|
|
|
19905
20261
|
errors++;
|
|
19906
20262
|
}
|
|
19907
20263
|
}
|
|
19908
|
-
} else if (
|
|
20264
|
+
} else if (existsSync48(stateDir)) {
|
|
19909
20265
|
try {
|
|
19910
|
-
|
|
20266
|
+
rmSync13(stateDir, { recursive: true, force: true });
|
|
19911
20267
|
console.log(` Removed ${stateDir}`);
|
|
19912
20268
|
} catch (err) {
|
|
19913
20269
|
console.warn(` Failed to remove ${stateDir}: ${err instanceof Error ? err.message : err}`);
|
|
@@ -19927,9 +20283,9 @@ This will remove all Runwork configuration from your local agents:
|
|
|
19927
20283
|
// src/commands/apps.ts
|
|
19928
20284
|
init_store();
|
|
19929
20285
|
init_client();
|
|
19930
|
-
import { Command as
|
|
20286
|
+
import { Command as Command29 } from "commander";
|
|
19931
20287
|
init_init();
|
|
19932
|
-
var listCommand12 = new
|
|
20288
|
+
var listCommand12 = new Command29("list").description("List apps in workspace").option("--workspace <name-or-id>", "Workspace name or ID").action(async (opts, command) => {
|
|
19933
20289
|
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
19934
20290
|
const credentials = requireAuth();
|
|
19935
20291
|
const client = new ApiClient(credentials);
|
|
@@ -19959,10 +20315,10 @@ Workspace: ${workspaceName || workspaceId}
|
|
|
19959
20315
|
process.exit(1);
|
|
19960
20316
|
}
|
|
19961
20317
|
});
|
|
19962
|
-
var createCommand3 = new
|
|
20318
|
+
var createCommand3 = new Command29("create").description("Create a new Runwork app").argument("[name]", "App name").action(async (name) => {
|
|
19963
20319
|
await runCreateFlow(name);
|
|
19964
20320
|
});
|
|
19965
|
-
var infoCommand2 = new
|
|
20321
|
+
var infoCommand2 = new Command29("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) => {
|
|
19966
20322
|
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
19967
20323
|
const credentials = requireAuth();
|
|
19968
20324
|
const client = new ApiClient(credentials);
|
|
@@ -19987,12 +20343,12 @@ var infoCommand2 = new Command28("info").description("Show detailed app info, pr
|
|
|
19987
20343
|
}
|
|
19988
20344
|
printAppInfo(data);
|
|
19989
20345
|
});
|
|
19990
|
-
var appsCommand = new
|
|
20346
|
+
var appsCommand = new Command29("apps").description("Manage workspace apps").addCommand(listCommand12).addCommand(createCommand3).addCommand(infoCommand2);
|
|
19991
20347
|
|
|
19992
20348
|
// src/commands/members.ts
|
|
19993
20349
|
init_store();
|
|
19994
20350
|
init_client();
|
|
19995
|
-
import { Command as
|
|
20351
|
+
import { Command as Command30 } from "commander";
|
|
19996
20352
|
function formatMemberRows(members) {
|
|
19997
20353
|
return members.map((m) => ({
|
|
19998
20354
|
name: m.user.displayName || m.user.email,
|
|
@@ -20002,7 +20358,7 @@ function formatMemberRows(members) {
|
|
|
20002
20358
|
userId: m.userId
|
|
20003
20359
|
}));
|
|
20004
20360
|
}
|
|
20005
|
-
var listCommand13 = new
|
|
20361
|
+
var listCommand13 = new Command30("list").description("List members of a workspace (name, email, role)").option("--workspace <name-or-id>", "Workspace name or ID").action(async (opts, command) => {
|
|
20006
20362
|
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
20007
20363
|
const credentials = requireAuth();
|
|
20008
20364
|
const client = new ApiClient(credentials);
|
|
@@ -20031,13 +20387,13 @@ Workspace: ${workspaceName || workspaceId}
|
|
|
20031
20387
|
process.exit(1);
|
|
20032
20388
|
}
|
|
20033
20389
|
});
|
|
20034
|
-
var membersCommand = new
|
|
20390
|
+
var membersCommand = new Command30("members").description("List workspace members").addCommand(listCommand13);
|
|
20035
20391
|
|
|
20036
20392
|
// src/commands/api.ts
|
|
20037
20393
|
init_store();
|
|
20038
20394
|
init_client();
|
|
20039
|
-
import { Command as
|
|
20040
|
-
import { readFileSync as
|
|
20395
|
+
import { Command as Command31 } from "commander";
|
|
20396
|
+
import { readFileSync as readFileSync38 } from "fs";
|
|
20041
20397
|
function normalizeApiPath(rawPath, baseUrl) {
|
|
20042
20398
|
if (/^https?:\/\//i.test(rawPath)) {
|
|
20043
20399
|
const target = new URL(rawPath);
|
|
@@ -20049,7 +20405,7 @@ function normalizeApiPath(rawPath, baseUrl) {
|
|
|
20049
20405
|
}
|
|
20050
20406
|
return rawPath.startsWith("/") ? rawPath : `/${rawPath}`;
|
|
20051
20407
|
}
|
|
20052
|
-
var apiCommand = new
|
|
20408
|
+
var apiCommand = new Command31("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", `
|
|
20053
20409
|
Examples:
|
|
20054
20410
|
runwork api GET /api/workspaces
|
|
20055
20411
|
runwork api GET /api/workspaces/<id>/members
|
|
@@ -20070,7 +20426,7 @@ to be read or pasted manually. Prefer a dedicated command when one exists
|
|
|
20070
20426
|
let curlStr = opts.curl;
|
|
20071
20427
|
if (opts.curlFile) {
|
|
20072
20428
|
try {
|
|
20073
|
-
curlStr =
|
|
20429
|
+
curlStr = readFileSync38(opts.curlFile, "utf-8");
|
|
20074
20430
|
} catch (err) {
|
|
20075
20431
|
console.error(`Could not read --curl-file: ${err instanceof Error ? err.message : err}`);
|
|
20076
20432
|
process.exit(1);
|
|
@@ -20090,7 +20446,7 @@ to be read or pasted manually. Prefer a dedicated command when one exists
|
|
|
20090
20446
|
let raw = opts.body;
|
|
20091
20447
|
if (raw.startsWith("@")) {
|
|
20092
20448
|
try {
|
|
20093
|
-
raw =
|
|
20449
|
+
raw = readFileSync38(raw.slice(1), "utf-8");
|
|
20094
20450
|
} catch (err) {
|
|
20095
20451
|
console.error(`Could not read body file: ${err instanceof Error ? err.message : err}`);
|
|
20096
20452
|
process.exit(1);
|
|
@@ -20132,16 +20488,16 @@ to be read or pasted manually. Prefer a dedicated command when one exists
|
|
|
20132
20488
|
|
|
20133
20489
|
// src/commands/doctor.ts
|
|
20134
20490
|
init_colors();
|
|
20135
|
-
import { Command as
|
|
20491
|
+
import { Command as Command32 } from "commander";
|
|
20136
20492
|
|
|
20137
20493
|
// src/health/checks.ts
|
|
20138
20494
|
init_subprocess();
|
|
20139
20495
|
init_store();
|
|
20140
20496
|
init_client();
|
|
20141
20497
|
import { parse as parse2 } from "smol-toml";
|
|
20142
|
-
import { existsSync as
|
|
20143
|
-
import { join as
|
|
20144
|
-
import { homedir as
|
|
20498
|
+
import { existsSync as existsSync49, readFileSync as readFileSync39 } from "fs";
|
|
20499
|
+
import { join as join45, sep as sep4 } from "path";
|
|
20500
|
+
import { homedir as homedir24, platform as osPlatform2, arch as osArch } from "os";
|
|
20145
20501
|
init_http();
|
|
20146
20502
|
init_preflight();
|
|
20147
20503
|
init_credentials();
|
|
@@ -20172,10 +20528,10 @@ function buildContext() {
|
|
|
20172
20528
|
const credentials = getCredentials();
|
|
20173
20529
|
const client = credentials ? new ApiClient(credentials) : null;
|
|
20174
20530
|
let config = null;
|
|
20175
|
-
const configPath =
|
|
20176
|
-
if (
|
|
20531
|
+
const configPath = join45(process.cwd(), ".runwork.json");
|
|
20532
|
+
if (existsSync49(configPath)) {
|
|
20177
20533
|
try {
|
|
20178
|
-
config = JSON.parse(
|
|
20534
|
+
config = JSON.parse(readFileSync39(configPath, "utf-8"));
|
|
20179
20535
|
} catch {}
|
|
20180
20536
|
}
|
|
20181
20537
|
return { credentials, client, config, cwd: process.cwd() };
|
|
@@ -20276,9 +20632,9 @@ async function checkCliArtifactReachable() {
|
|
|
20276
20632
|
}
|
|
20277
20633
|
async function checkCliInstallLocation() {
|
|
20278
20634
|
const isWindows2 = osPlatform2() === "win32";
|
|
20279
|
-
const home =
|
|
20280
|
-
const canonicalDir =
|
|
20281
|
-
const canonicalBinary = isWindows2 ?
|
|
20635
|
+
const home = homedir24();
|
|
20636
|
+
const canonicalDir = join45(home, ".runwork", "bin");
|
|
20637
|
+
const canonicalBinary = isWindows2 ? join45(canonicalDir, "runwork.exe") : join45(canonicalDir, "runwork");
|
|
20282
20638
|
const candidates = [process.execPath, process.argv[1] || ""].filter(Boolean);
|
|
20283
20639
|
const runsFromCanonical = candidates.some((p) => normalizePath(p) === normalizePath(canonicalBinary));
|
|
20284
20640
|
if (runsFromCanonical) {
|
|
@@ -20288,7 +20644,7 @@ async function checkCliInstallLocation() {
|
|
|
20288
20644
|
message: `canonical (${canonicalBinary})`
|
|
20289
20645
|
};
|
|
20290
20646
|
}
|
|
20291
|
-
if (
|
|
20647
|
+
if (existsSync49(canonicalBinary)) {
|
|
20292
20648
|
return {
|
|
20293
20649
|
name: "cli-install-location",
|
|
20294
20650
|
status: "warn",
|
|
@@ -20410,8 +20766,8 @@ async function checkGitCredentialHelper(ctx) {
|
|
|
20410
20766
|
};
|
|
20411
20767
|
}
|
|
20412
20768
|
async function checkProjectConfig(ctx) {
|
|
20413
|
-
const configPath =
|
|
20414
|
-
if (!
|
|
20769
|
+
const configPath = join45(ctx.cwd, ".runwork.json");
|
|
20770
|
+
if (!existsSync49(configPath)) {
|
|
20415
20771
|
if (!ctx.credentials) {
|
|
20416
20772
|
return { name: "project-config", status: "skip", message: "no project (not logged in)" };
|
|
20417
20773
|
}
|
|
@@ -20473,7 +20829,7 @@ async function checkGitRemote(ctx) {
|
|
|
20473
20829
|
if (!ctx.config) {
|
|
20474
20830
|
return { name: "git-remote", status: "skip", message: "skipped (no project)" };
|
|
20475
20831
|
}
|
|
20476
|
-
if (!
|
|
20832
|
+
if (!existsSync49(join45(ctx.cwd, ".git"))) {
|
|
20477
20833
|
return {
|
|
20478
20834
|
name: "git-remote",
|
|
20479
20835
|
status: "fail",
|
|
@@ -20527,12 +20883,12 @@ async function checkDeployFreshness(ctx) {
|
|
|
20527
20883
|
return { name: "deploy-freshness", status: "skip", message: "local HEAD unknown" };
|
|
20528
20884
|
}
|
|
20529
20885
|
function loadSetupState5() {
|
|
20530
|
-
const projectPath =
|
|
20531
|
-
const userPath =
|
|
20886
|
+
const projectPath = join45(process.cwd(), ".runwork", "setup.json");
|
|
20887
|
+
const userPath = join45(homedir24(), ".runwork", "setup.json");
|
|
20532
20888
|
for (const p of [projectPath, userPath]) {
|
|
20533
|
-
if (
|
|
20889
|
+
if (existsSync49(p)) {
|
|
20534
20890
|
try {
|
|
20535
|
-
return JSON.parse(
|
|
20891
|
+
return JSON.parse(readFileSync39(p, "utf-8"));
|
|
20536
20892
|
} catch {
|
|
20537
20893
|
continue;
|
|
20538
20894
|
}
|
|
@@ -20547,13 +20903,13 @@ async function checkCodexNetwork() {
|
|
|
20547
20903
|
if (!state || !state.configuredAgents.includes("codex")) {
|
|
20548
20904
|
return { name, status: "skip", message: "Codex not configured for Runwork" };
|
|
20549
20905
|
}
|
|
20550
|
-
const configPath =
|
|
20551
|
-
if (!
|
|
20906
|
+
const configPath = join45(homedir24(), ".codex", "config.toml");
|
|
20907
|
+
if (!existsSync49(configPath)) {
|
|
20552
20908
|
return { name, status: "skip", message: "no Codex config found" };
|
|
20553
20909
|
}
|
|
20554
20910
|
let parsed;
|
|
20555
20911
|
try {
|
|
20556
|
-
parsed = parse2(
|
|
20912
|
+
parsed = parse2(readFileSync39(configPath, "utf-8"));
|
|
20557
20913
|
} catch {
|
|
20558
20914
|
return { name, status: "warn", message: "could not parse ~/.codex/config.toml" };
|
|
20559
20915
|
}
|
|
@@ -20606,19 +20962,19 @@ async function checkCodexDesktopProject() {
|
|
|
20606
20962
|
if (!usesCodex) {
|
|
20607
20963
|
return { name, status: "skip", message: "Codex not configured for Runwork" };
|
|
20608
20964
|
}
|
|
20609
|
-
const statePath2 =
|
|
20610
|
-
if (!
|
|
20965
|
+
const statePath2 = join45(homedir24(), ".codex", ".codex-global-state.json");
|
|
20966
|
+
if (!existsSync49(statePath2)) {
|
|
20611
20967
|
return { name, status: "skip", message: "Codex desktop app not detected" };
|
|
20612
20968
|
}
|
|
20613
20969
|
let savedRoots = [];
|
|
20614
20970
|
try {
|
|
20615
|
-
const parsed = JSON.parse(
|
|
20971
|
+
const parsed = JSON.parse(readFileSync39(statePath2, "utf-8"));
|
|
20616
20972
|
const roots = parsed["electron-saved-workspace-roots"];
|
|
20617
20973
|
savedRoots = Array.isArray(roots) ? roots.filter((r) => typeof r === "string") : [];
|
|
20618
20974
|
} catch {
|
|
20619
20975
|
return { name, status: "warn", message: "could not read Codex desktop state" };
|
|
20620
20976
|
}
|
|
20621
|
-
const runworkDir =
|
|
20977
|
+
const runworkDir = join45(homedir24(), ".runwork");
|
|
20622
20978
|
if (savedRoots.includes(runworkDir)) {
|
|
20623
20979
|
return { name, status: "pass", message: "Runwork project added to Codex desktop sidebar" };
|
|
20624
20980
|
}
|
|
@@ -20671,9 +21027,9 @@ async function checkAgentSetup() {
|
|
|
20671
21027
|
if (!adapter2 || !adapter2.supportsMcpScope("user"))
|
|
20672
21028
|
continue;
|
|
20673
21029
|
const mcpConfigPath = getMcpConfigPath2(slug, "user");
|
|
20674
|
-
if (mcpConfigPath &&
|
|
21030
|
+
if (mcpConfigPath && existsSync49(mcpConfigPath)) {
|
|
20675
21031
|
try {
|
|
20676
|
-
const content =
|
|
21032
|
+
const content = readFileSync39(mcpConfigPath, "utf-8");
|
|
20677
21033
|
const missingMcp = state.mcpServers.filter((name) => !content.includes(name));
|
|
20678
21034
|
if (missingMcp.length > 0) {
|
|
20679
21035
|
details.push(`${missingMcp.length} MCP server(s) missing from ${slug} config`);
|
|
@@ -20696,8 +21052,8 @@ async function checkAgentSetup() {
|
|
|
20696
21052
|
if (!skillsDir)
|
|
20697
21053
|
continue;
|
|
20698
21054
|
const missingSkills = state.skills.filter((name) => {
|
|
20699
|
-
const skillPath =
|
|
20700
|
-
return !
|
|
21055
|
+
const skillPath = join45(skillsDir, name, "SKILL.md");
|
|
21056
|
+
return !existsSync49(skillPath);
|
|
20701
21057
|
});
|
|
20702
21058
|
if (missingSkills.length > 0) {
|
|
20703
21059
|
details.push(`${missingSkills.length} skill(s) missing from ${slug}`);
|
|
@@ -20722,33 +21078,66 @@ async function checkAgentSetup() {
|
|
|
20722
21078
|
};
|
|
20723
21079
|
}
|
|
20724
21080
|
function getMcpConfigPath2(slug, scope) {
|
|
20725
|
-
const home =
|
|
21081
|
+
const home = homedir24();
|
|
20726
21082
|
switch (slug) {
|
|
20727
21083
|
case "claude-code":
|
|
20728
|
-
return scope === "project" ?
|
|
21084
|
+
return scope === "project" ? join45(process.cwd(), ".mcp.json") : join45(home, ".claude", "settings.json");
|
|
20729
21085
|
case "cursor":
|
|
20730
|
-
return scope === "project" ?
|
|
21086
|
+
return scope === "project" ? join45(process.cwd(), ".cursor", "mcp.json") : join45(home, ".cursor", "mcp.json");
|
|
20731
21087
|
case "windsurf":
|
|
20732
|
-
return scope === "project" ?
|
|
21088
|
+
return scope === "project" ? join45(process.cwd(), ".windsurf", "mcp.json") : join45(home, ".windsurf", "mcp.json");
|
|
20733
21089
|
case "codex":
|
|
20734
21090
|
case "codex-app":
|
|
20735
|
-
return scope === "user" ?
|
|
21091
|
+
return scope === "user" ? join45(home, ".codex", "config.toml") : null;
|
|
20736
21092
|
case "gemini":
|
|
20737
|
-
return scope === "user" ?
|
|
21093
|
+
return scope === "user" ? join45(home, ".gemini", "settings.json") : null;
|
|
20738
21094
|
default:
|
|
20739
21095
|
return null;
|
|
20740
21096
|
}
|
|
20741
21097
|
}
|
|
21098
|
+
async function checkWorkspacePointers() {
|
|
21099
|
+
const userStatePath = join45(homedir24(), ".runwork", "setup.json");
|
|
21100
|
+
const state = existsSync49(userStatePath) ? (() => {
|
|
21101
|
+
try {
|
|
21102
|
+
return JSON.parse(readFileSync39(userStatePath, "utf-8"));
|
|
21103
|
+
} catch {
|
|
21104
|
+
return null;
|
|
21105
|
+
}
|
|
21106
|
+
})() : null;
|
|
21107
|
+
const credentials = getCredentials();
|
|
21108
|
+
if (!state) {
|
|
21109
|
+
return { name: "workspace-pointers", status: "skip", message: "setup has not run on this machine" };
|
|
21110
|
+
}
|
|
21111
|
+
if (!credentials?.defaultWorkspaceId) {
|
|
21112
|
+
return { name: "workspace-pointers", status: "skip", message: "no default workspace in credentials" };
|
|
21113
|
+
}
|
|
21114
|
+
if (credentials.defaultWorkspaceId === state.workspaceId) {
|
|
21115
|
+
const label = state.workspaceName || state.workspaceId;
|
|
21116
|
+
return { name: "workspace-pointers", status: "pass", message: `${label} everywhere` };
|
|
21117
|
+
}
|
|
21118
|
+
const configured = state.workspaceName || state.workspaceId;
|
|
21119
|
+
const selected = credentials.defaultWorkspaceName || credentials.defaultWorkspaceId;
|
|
21120
|
+
return {
|
|
21121
|
+
name: "workspace-pointers",
|
|
21122
|
+
status: "fail",
|
|
21123
|
+
message: `your AI tools are set up for ${configured}, but ${selected} is selected`,
|
|
21124
|
+
details: [
|
|
21125
|
+
`setup.json (drives sync): ${state.workspaceId}`,
|
|
21126
|
+
`credentials (selected): ${credentials.defaultWorkspaceId}`
|
|
21127
|
+
],
|
|
21128
|
+
fix: `runwork workspace switch ${credentials.defaultWorkspaceId}`
|
|
21129
|
+
};
|
|
21130
|
+
}
|
|
20742
21131
|
function getSkillsDir(slug, scope) {
|
|
20743
|
-
const home =
|
|
21132
|
+
const home = homedir24();
|
|
20744
21133
|
switch (slug) {
|
|
20745
21134
|
case "claude-code":
|
|
20746
|
-
return scope === "project" ?
|
|
21135
|
+
return scope === "project" ? join45(process.cwd(), ".claude", "skills") : join45(home, ".claude", "skills");
|
|
20747
21136
|
case "codex":
|
|
20748
21137
|
case "codex-app":
|
|
20749
|
-
return scope === "project" ?
|
|
21138
|
+
return scope === "project" ? join45(process.cwd(), ".agents", "skills") : join45(home, ".agents", "skills");
|
|
20750
21139
|
case "gemini":
|
|
20751
|
-
return scope === "project" ?
|
|
21140
|
+
return scope === "project" ? join45(process.cwd(), ".gemini", "skills") : join45(home, ".gemini", "skills");
|
|
20752
21141
|
default:
|
|
20753
21142
|
return null;
|
|
20754
21143
|
}
|
|
@@ -20774,7 +21163,8 @@ var CHECK_RUNNERS = [
|
|
|
20774
21163
|
{ names: ["deploy-freshness"], run: async (ctx) => [await checkDeployFreshness(ctx)] },
|
|
20775
21164
|
{ names: ["agent-setup"], run: async () => [await checkAgentSetup()] },
|
|
20776
21165
|
{ names: ["codex-network"], run: async () => [await checkCodexNetwork()] },
|
|
20777
|
-
{ names: ["codex-desktop-project"], run: async () => [await checkCodexDesktopProject()] }
|
|
21166
|
+
{ names: ["codex-desktop-project"], run: async () => [await checkCodexDesktopProject()] },
|
|
21167
|
+
{ names: ["workspace-pointers"], run: async () => [await checkWorkspacePointers()] }
|
|
20778
21168
|
];
|
|
20779
21169
|
var ALL_CHECK_NAMES = CHECK_RUNNERS.flatMap((r) => r.names);
|
|
20780
21170
|
async function runAllChecks(options) {
|
|
@@ -20800,8 +21190,8 @@ async function runAllChecks(options) {
|
|
|
20800
21190
|
// src/health/fix.ts
|
|
20801
21191
|
init_credentials();
|
|
20802
21192
|
init_remote();
|
|
20803
|
-
import { existsSync as
|
|
20804
|
-
import { join as
|
|
21193
|
+
import { existsSync as existsSync50 } from "fs";
|
|
21194
|
+
import { join as join46 } from "path";
|
|
20805
21195
|
async function applyDoctorFixes(ctx, failingNames) {
|
|
20806
21196
|
const failing = new Set(failingNames);
|
|
20807
21197
|
const outcomes = [];
|
|
@@ -20828,7 +21218,7 @@ async function applyDoctorFixes(ctx, failingNames) {
|
|
|
20828
21218
|
applied: false,
|
|
20829
21219
|
message: "no project config -- run inside an app directory"
|
|
20830
21220
|
});
|
|
20831
|
-
} else if (!
|
|
21221
|
+
} else if (!existsSync50(join46(ctx.cwd, ".git"))) {
|
|
20832
21222
|
outcomes.push({
|
|
20833
21223
|
name: "git-remote",
|
|
20834
21224
|
applied: false,
|
|
@@ -20847,10 +21237,10 @@ async function applyDoctorFixes(ctx, failingNames) {
|
|
|
20847
21237
|
}
|
|
20848
21238
|
|
|
20849
21239
|
// src/agents/runtime-detection.ts
|
|
20850
|
-
import { existsSync as
|
|
20851
|
-
import { homedir as
|
|
20852
|
-
import { join as
|
|
20853
|
-
var RUNWORK_SESSIONS_DIR =
|
|
21240
|
+
import { existsSync as existsSync51, readFileSync as readFileSync40, statSync as statSync9, readdirSync as readdirSync15 } from "fs";
|
|
21241
|
+
import { homedir as homedir25 } from "os";
|
|
21242
|
+
import { join as join47 } from "path";
|
|
21243
|
+
var RUNWORK_SESSIONS_DIR = join47(homedir25(), ".runwork", "sessions");
|
|
20854
21244
|
function detectCurrentAgent() {
|
|
20855
21245
|
const claudeCodeSessionId = process.env.CLAUDE_CODE_SESSION_ID;
|
|
20856
21246
|
if (claudeCodeSessionId) {
|
|
@@ -20913,11 +21303,11 @@ function detectCurrentAgent() {
|
|
|
20913
21303
|
return null;
|
|
20914
21304
|
}
|
|
20915
21305
|
function readHookSessionInfo(sessionId) {
|
|
20916
|
-
const path4 =
|
|
20917
|
-
if (!
|
|
21306
|
+
const path4 = join47(RUNWORK_SESSIONS_DIR, `${sessionId}.json`);
|
|
21307
|
+
if (!existsSync51(path4))
|
|
20918
21308
|
return null;
|
|
20919
21309
|
try {
|
|
20920
|
-
const raw =
|
|
21310
|
+
const raw = readFileSync40(path4, "utf8");
|
|
20921
21311
|
const parsed = JSON.parse(raw);
|
|
20922
21312
|
return parsed;
|
|
20923
21313
|
} catch {
|
|
@@ -20925,40 +21315,40 @@ function readHookSessionInfo(sessionId) {
|
|
|
20925
21315
|
}
|
|
20926
21316
|
}
|
|
20927
21317
|
function findClaudeCodeSessionFile(sessionId) {
|
|
20928
|
-
const root =
|
|
20929
|
-
if (!
|
|
21318
|
+
const root = join47(homedir25(), ".claude", "projects");
|
|
21319
|
+
if (!existsSync51(root))
|
|
20930
21320
|
return null;
|
|
20931
21321
|
let projectDirs;
|
|
20932
21322
|
try {
|
|
20933
|
-
projectDirs =
|
|
21323
|
+
projectDirs = readdirSync15(root);
|
|
20934
21324
|
} catch {
|
|
20935
21325
|
return null;
|
|
20936
21326
|
}
|
|
20937
21327
|
for (const dir of projectDirs) {
|
|
20938
|
-
const candidate =
|
|
20939
|
-
if (
|
|
21328
|
+
const candidate = join47(root, dir, `${sessionId}.jsonl`);
|
|
21329
|
+
if (existsSync51(candidate))
|
|
20940
21330
|
return candidate;
|
|
20941
21331
|
}
|
|
20942
21332
|
return null;
|
|
20943
21333
|
}
|
|
20944
21334
|
function findCodexRolloutFile(threadId) {
|
|
20945
|
-
const root =
|
|
20946
|
-
if (!
|
|
21335
|
+
const root = join47(homedir25(), ".codex", "sessions");
|
|
21336
|
+
if (!existsSync51(root))
|
|
20947
21337
|
return null;
|
|
20948
21338
|
const stack = [root];
|
|
20949
21339
|
while (stack.length > 0) {
|
|
20950
21340
|
const dir = stack.pop();
|
|
20951
21341
|
let entries;
|
|
20952
21342
|
try {
|
|
20953
|
-
entries =
|
|
21343
|
+
entries = readdirSync15(dir);
|
|
20954
21344
|
} catch {
|
|
20955
21345
|
continue;
|
|
20956
21346
|
}
|
|
20957
21347
|
for (const entry of entries) {
|
|
20958
|
-
const full =
|
|
21348
|
+
const full = join47(dir, entry);
|
|
20959
21349
|
let s;
|
|
20960
21350
|
try {
|
|
20961
|
-
s =
|
|
21351
|
+
s = statSync9(full);
|
|
20962
21352
|
} catch {
|
|
20963
21353
|
continue;
|
|
20964
21354
|
}
|
|
@@ -20972,30 +21362,30 @@ function findCodexRolloutFile(threadId) {
|
|
|
20972
21362
|
return null;
|
|
20973
21363
|
}
|
|
20974
21364
|
function findNewestClaudeCodeSession() {
|
|
20975
|
-
const root =
|
|
20976
|
-
if (!
|
|
21365
|
+
const root = join47(homedir25(), ".claude", "projects");
|
|
21366
|
+
if (!existsSync51(root))
|
|
20977
21367
|
return null;
|
|
20978
21368
|
let projectDirs;
|
|
20979
21369
|
try {
|
|
20980
|
-
projectDirs =
|
|
21370
|
+
projectDirs = readdirSync15(root);
|
|
20981
21371
|
} catch {
|
|
20982
21372
|
return null;
|
|
20983
21373
|
}
|
|
20984
21374
|
let best = null;
|
|
20985
21375
|
for (const dir of projectDirs) {
|
|
20986
|
-
const projectPath =
|
|
21376
|
+
const projectPath = join47(root, dir);
|
|
20987
21377
|
let files;
|
|
20988
21378
|
try {
|
|
20989
|
-
files =
|
|
21379
|
+
files = readdirSync15(projectPath);
|
|
20990
21380
|
} catch {
|
|
20991
21381
|
continue;
|
|
20992
21382
|
}
|
|
20993
21383
|
for (const file of files) {
|
|
20994
21384
|
if (!file.endsWith(".jsonl"))
|
|
20995
21385
|
continue;
|
|
20996
|
-
const full =
|
|
21386
|
+
const full = join47(projectPath, file);
|
|
20997
21387
|
try {
|
|
20998
|
-
const s =
|
|
21388
|
+
const s = statSync9(full);
|
|
20999
21389
|
if (!best || s.mtimeMs > best.mtime) {
|
|
21000
21390
|
best = {
|
|
21001
21391
|
sessionId: file.replace(/\.jsonl$/, ""),
|
|
@@ -21011,8 +21401,8 @@ function findNewestClaudeCodeSession() {
|
|
|
21011
21401
|
return best ? { sessionId: best.sessionId, path: best.path } : null;
|
|
21012
21402
|
}
|
|
21013
21403
|
function findNewestCodexRollout() {
|
|
21014
|
-
const root =
|
|
21015
|
-
if (!
|
|
21404
|
+
const root = join47(homedir25(), ".codex", "sessions");
|
|
21405
|
+
if (!existsSync51(root))
|
|
21016
21406
|
return null;
|
|
21017
21407
|
const stack = [root];
|
|
21018
21408
|
let best = null;
|
|
@@ -21020,15 +21410,15 @@ function findNewestCodexRollout() {
|
|
|
21020
21410
|
const dir = stack.pop();
|
|
21021
21411
|
let entries;
|
|
21022
21412
|
try {
|
|
21023
|
-
entries =
|
|
21413
|
+
entries = readdirSync15(dir);
|
|
21024
21414
|
} catch {
|
|
21025
21415
|
continue;
|
|
21026
21416
|
}
|
|
21027
21417
|
for (const entry of entries) {
|
|
21028
|
-
const full =
|
|
21418
|
+
const full = join47(dir, entry);
|
|
21029
21419
|
let s;
|
|
21030
21420
|
try {
|
|
21031
|
-
s =
|
|
21421
|
+
s = statSync9(full);
|
|
21032
21422
|
} catch {
|
|
21033
21423
|
continue;
|
|
21034
21424
|
}
|
|
@@ -21204,7 +21594,7 @@ function parseCheckNames(raw) {
|
|
|
21204
21594
|
const unknown = requested.filter((n) => !known.has(n));
|
|
21205
21595
|
return { only, unknown };
|
|
21206
21596
|
}
|
|
21207
|
-
var doctorCommand = new
|
|
21597
|
+
var doctorCommand = new Command32("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) => {
|
|
21208
21598
|
const asJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
21209
21599
|
let only;
|
|
21210
21600
|
if (opts.check) {
|
|
@@ -21255,8 +21645,8 @@ var doctorCommand = new Command31("doctor").description("Check system health: au
|
|
|
21255
21645
|
// src/commands/share-convo.ts
|
|
21256
21646
|
init_store();
|
|
21257
21647
|
init_client();
|
|
21258
|
-
import { Command as
|
|
21259
|
-
import { readFileSync as
|
|
21648
|
+
import { Command as Command33 } from "commander";
|
|
21649
|
+
import { readFileSync as readFileSync41, existsSync as existsSync52 } from "fs";
|
|
21260
21650
|
import { createHash as createHash4 } from "crypto";
|
|
21261
21651
|
function nativeBundleFormatForAgent(slug) {
|
|
21262
21652
|
if (slug === "claude-code" || slug === "claude-desktop")
|
|
@@ -21277,7 +21667,7 @@ async function runShareConvo(opts, command, isPersonalAlias = false) {
|
|
|
21277
21667
|
console.error("Error: --transcript-file is required. Pass the path to the LLM-emitted markdown transcript.");
|
|
21278
21668
|
process.exit(1);
|
|
21279
21669
|
}
|
|
21280
|
-
if (!
|
|
21670
|
+
if (!existsSync52(opts.transcriptFile)) {
|
|
21281
21671
|
console.error(`Error: transcript file does not exist: ${opts.transcriptFile}`);
|
|
21282
21672
|
process.exit(1);
|
|
21283
21673
|
}
|
|
@@ -21298,7 +21688,7 @@ async function runShareConvo(opts, command, isPersonalAlias = false) {
|
|
|
21298
21688
|
const credentials = requireAuth();
|
|
21299
21689
|
const client = new ApiClient(credentials);
|
|
21300
21690
|
const { workspaceId } = await resolveWorkspace2(client, { workspace: opts.workspace });
|
|
21301
|
-
const transcriptContent =
|
|
21691
|
+
const transcriptContent = readFileSync41(opts.transcriptFile, "utf8");
|
|
21302
21692
|
const bundles = [
|
|
21303
21693
|
{
|
|
21304
21694
|
format: "transcript",
|
|
@@ -21311,19 +21701,19 @@ async function runShareConvo(opts, command, isPersonalAlias = false) {
|
|
|
21311
21701
|
const sourceAgent = opts.sourceAgent ?? detected?.slug ?? "generic";
|
|
21312
21702
|
let nativeFilePath = null;
|
|
21313
21703
|
if (opts.nativeFile) {
|
|
21314
|
-
if (!
|
|
21704
|
+
if (!existsSync52(opts.nativeFile)) {
|
|
21315
21705
|
console.error(`Error: --native-file path does not exist: ${opts.nativeFile}`);
|
|
21316
21706
|
process.exit(1);
|
|
21317
21707
|
}
|
|
21318
21708
|
nativeFilePath = opts.nativeFile;
|
|
21319
|
-
} else if (detected?.sessionFilePath &&
|
|
21709
|
+
} else if (detected?.sessionFilePath && existsSync52(detected.sessionFilePath)) {
|
|
21320
21710
|
nativeFilePath = detected.sessionFilePath;
|
|
21321
21711
|
}
|
|
21322
21712
|
if (nativeFilePath) {
|
|
21323
21713
|
const nativeFormat = nativeBundleFormatForAgent(sourceAgent);
|
|
21324
21714
|
if (nativeFormat) {
|
|
21325
21715
|
try {
|
|
21326
|
-
const content =
|
|
21716
|
+
const content = readFileSync41(nativeFilePath, "utf8");
|
|
21327
21717
|
bundles.push({
|
|
21328
21718
|
format: nativeFormat,
|
|
21329
21719
|
content,
|
|
@@ -21339,7 +21729,7 @@ async function runShareConvo(opts, command, isPersonalAlias = false) {
|
|
|
21339
21729
|
let metadata = {};
|
|
21340
21730
|
if (opts.metadataFile) {
|
|
21341
21731
|
try {
|
|
21342
|
-
metadata = JSON.parse(
|
|
21732
|
+
metadata = JSON.parse(readFileSync41(opts.metadataFile, "utf8"));
|
|
21343
21733
|
} catch (err) {
|
|
21344
21734
|
console.error(`Error: --metadata-file is not valid JSON: ${err instanceof Error ? err.message : err}`);
|
|
21345
21735
|
process.exit(1);
|
|
@@ -21394,17 +21784,17 @@ Skipped: ${result.skipped.map((s) => `${s.identifier} (${s.reason})`).join(", ")
|
|
|
21394
21784
|
process.exit(1);
|
|
21395
21785
|
}
|
|
21396
21786
|
}
|
|
21397
|
-
var shareConvoCommand = new
|
|
21787
|
+
var shareConvoCommand = new Command33("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));
|
|
21398
21788
|
|
|
21399
21789
|
// src/commands/save-convo.ts
|
|
21400
|
-
import { Command as
|
|
21401
|
-
var saveConvoCommand = new
|
|
21790
|
+
import { Command as Command34 } from "commander";
|
|
21791
|
+
var saveConvoCommand = new Command34("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));
|
|
21402
21792
|
|
|
21403
21793
|
// src/commands/inbox.ts
|
|
21404
21794
|
init_store();
|
|
21405
21795
|
init_client();
|
|
21406
|
-
import { Command as
|
|
21407
|
-
var inboxCommand = new
|
|
21796
|
+
import { Command as Command35 } from "commander";
|
|
21797
|
+
var inboxCommand = new Command35("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) => {
|
|
21408
21798
|
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
21409
21799
|
const scope = opts.filter === "received" || opts.filter === "sent" || opts.filter === "saved" ? opts.filter : "all";
|
|
21410
21800
|
const limit = opts.limit ? parseInt(opts.limit, 10) : 50;
|
|
@@ -21444,10 +21834,10 @@ Shared conversations (${scope}, ${total}):
|
|
|
21444
21834
|
// src/commands/resume.ts
|
|
21445
21835
|
init_store();
|
|
21446
21836
|
init_client();
|
|
21447
|
-
import { Command as
|
|
21448
|
-
import { writeFileSync as
|
|
21449
|
-
import { homedir as
|
|
21450
|
-
import { join as
|
|
21837
|
+
import { Command as Command36 } from "commander";
|
|
21838
|
+
import { writeFileSync as writeFileSync29, mkdirSync as mkdirSync27, realpathSync } from "fs";
|
|
21839
|
+
import { homedir as homedir26 } from "os";
|
|
21840
|
+
import { join as join48 } from "path";
|
|
21451
21841
|
import { spawn as spawn5 } from "child_process";
|
|
21452
21842
|
function encodeClaudeCodeCwd(cwd) {
|
|
21453
21843
|
let canonical;
|
|
@@ -21484,10 +21874,10 @@ function extractCodexUuid(rolloutContent) {
|
|
|
21484
21874
|
}
|
|
21485
21875
|
function placeClaudeJsonl(uuid, content, recipientCwd) {
|
|
21486
21876
|
const encoded = encodeClaudeCodeCwd(recipientCwd);
|
|
21487
|
-
const projectDir =
|
|
21488
|
-
|
|
21489
|
-
const placedAt =
|
|
21490
|
-
|
|
21877
|
+
const projectDir = join48(homedir26(), ".claude", "projects", encoded);
|
|
21878
|
+
mkdirSync27(projectDir, { recursive: true });
|
|
21879
|
+
const placedAt = join48(projectDir, `${uuid}.jsonl`);
|
|
21880
|
+
writeFileSync29(placedAt, content);
|
|
21491
21881
|
return { placedAt, runFromCwd: recipientCwd };
|
|
21492
21882
|
}
|
|
21493
21883
|
function placeCodexRollout(uuid, content) {
|
|
@@ -21495,11 +21885,11 @@ function placeCodexRollout(uuid, content) {
|
|
|
21495
21885
|
const yyyy = String(now.getUTCFullYear());
|
|
21496
21886
|
const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
|
|
21497
21887
|
const dd = String(now.getUTCDate()).padStart(2, "0");
|
|
21498
|
-
const dir =
|
|
21499
|
-
|
|
21888
|
+
const dir = join48(homedir26(), ".codex", "sessions", yyyy, mm, dd);
|
|
21889
|
+
mkdirSync27(dir, { recursive: true });
|
|
21500
21890
|
const ts = now.toISOString().replace(/\.\d+Z$/, "").replace(/:/g, "-");
|
|
21501
|
-
const placedAt =
|
|
21502
|
-
|
|
21891
|
+
const placedAt = join48(dir, `rollout-${ts}-${uuid}.jsonl`);
|
|
21892
|
+
writeFileSync29(placedAt, content);
|
|
21503
21893
|
return { placedAt };
|
|
21504
21894
|
}
|
|
21505
21895
|
function pickTargetAgent(opts, sourceAgent) {
|
|
@@ -21518,7 +21908,7 @@ function isAgentInstalled(agent) {
|
|
|
21518
21908
|
}
|
|
21519
21909
|
return false;
|
|
21520
21910
|
}
|
|
21521
|
-
var resumeCommand2 = new
|
|
21911
|
+
var resumeCommand2 = new Command36("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) => {
|
|
21522
21912
|
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
21523
21913
|
const credentials = requireAuth();
|
|
21524
21914
|
const client = new ApiClient(credentials);
|
|
@@ -21748,7 +22138,7 @@ process.on("uncaughtException", (err) => {
|
|
|
21748
22138
|
console.error(`Uncaught exception: ${formatError(err)}`);
|
|
21749
22139
|
process.exit(1);
|
|
21750
22140
|
});
|
|
21751
|
-
var program = new
|
|
22141
|
+
var program = new Command37;
|
|
21752
22142
|
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)");
|
|
21753
22143
|
program.addCommand(infoCommand);
|
|
21754
22144
|
program.addCommand(loginCommand);
|
|
@@ -21773,6 +22163,7 @@ program.addCommand(apiKeysCommand);
|
|
|
21773
22163
|
program.addCommand(agentsCommand);
|
|
21774
22164
|
program.addCommand(mcpCommand);
|
|
21775
22165
|
program.addCommand(setupCommand);
|
|
22166
|
+
program.addCommand(workspaceCommand);
|
|
21776
22167
|
program.addCommand(syncCommand);
|
|
21777
22168
|
program.addCommand(buildPluginCommand);
|
|
21778
22169
|
program.addCommand(uninstallCommand);
|