@factiii/runner 0.9.7 → 0.10.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/cli.js +2749 -825
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -963,8 +963,8 @@ var require_command = __commonJS({
|
|
|
963
963
|
"node_modules/commander/lib/command.js"(exports2) {
|
|
964
964
|
var EventEmitter = require("node:events").EventEmitter;
|
|
965
965
|
var childProcess = require("node:child_process");
|
|
966
|
-
var
|
|
967
|
-
var
|
|
966
|
+
var path17 = require("node:path");
|
|
967
|
+
var fs14 = require("node:fs");
|
|
968
968
|
var process2 = require("node:process");
|
|
969
969
|
var { Argument: Argument2, humanReadableArgName } = require_argument();
|
|
970
970
|
var { CommanderError: CommanderError2 } = require_error();
|
|
@@ -1896,11 +1896,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1896
1896
|
let launchWithNode = false;
|
|
1897
1897
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
1898
1898
|
function findFile(baseDir, baseName) {
|
|
1899
|
-
const localBin =
|
|
1900
|
-
if (
|
|
1901
|
-
if (sourceExt.includes(
|
|
1899
|
+
const localBin = path17.resolve(baseDir, baseName);
|
|
1900
|
+
if (fs14.existsSync(localBin)) return localBin;
|
|
1901
|
+
if (sourceExt.includes(path17.extname(baseName))) return void 0;
|
|
1902
1902
|
const foundExt = sourceExt.find(
|
|
1903
|
-
(ext) =>
|
|
1903
|
+
(ext) => fs14.existsSync(`${localBin}${ext}`)
|
|
1904
1904
|
);
|
|
1905
1905
|
if (foundExt) return `${localBin}${foundExt}`;
|
|
1906
1906
|
return void 0;
|
|
@@ -1912,21 +1912,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1912
1912
|
if (this._scriptPath) {
|
|
1913
1913
|
let resolvedScriptPath;
|
|
1914
1914
|
try {
|
|
1915
|
-
resolvedScriptPath =
|
|
1915
|
+
resolvedScriptPath = fs14.realpathSync(this._scriptPath);
|
|
1916
1916
|
} catch (err) {
|
|
1917
1917
|
resolvedScriptPath = this._scriptPath;
|
|
1918
1918
|
}
|
|
1919
|
-
executableDir =
|
|
1920
|
-
|
|
1919
|
+
executableDir = path17.resolve(
|
|
1920
|
+
path17.dirname(resolvedScriptPath),
|
|
1921
1921
|
executableDir
|
|
1922
1922
|
);
|
|
1923
1923
|
}
|
|
1924
1924
|
if (executableDir) {
|
|
1925
1925
|
let localFile = findFile(executableDir, executableFile);
|
|
1926
1926
|
if (!localFile && !subcommand._executableFile && this._scriptPath) {
|
|
1927
|
-
const legacyName =
|
|
1927
|
+
const legacyName = path17.basename(
|
|
1928
1928
|
this._scriptPath,
|
|
1929
|
-
|
|
1929
|
+
path17.extname(this._scriptPath)
|
|
1930
1930
|
);
|
|
1931
1931
|
if (legacyName !== this._name) {
|
|
1932
1932
|
localFile = findFile(
|
|
@@ -1937,7 +1937,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1937
1937
|
}
|
|
1938
1938
|
executableFile = localFile || executableFile;
|
|
1939
1939
|
}
|
|
1940
|
-
launchWithNode = sourceExt.includes(
|
|
1940
|
+
launchWithNode = sourceExt.includes(path17.extname(executableFile));
|
|
1941
1941
|
let proc;
|
|
1942
1942
|
if (process2.platform !== "win32") {
|
|
1943
1943
|
if (launchWithNode) {
|
|
@@ -2777,7 +2777,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2777
2777
|
* @return {Command}
|
|
2778
2778
|
*/
|
|
2779
2779
|
nameFromFilename(filename) {
|
|
2780
|
-
this._name =
|
|
2780
|
+
this._name = path17.basename(filename, path17.extname(filename));
|
|
2781
2781
|
return this;
|
|
2782
2782
|
}
|
|
2783
2783
|
/**
|
|
@@ -2791,9 +2791,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2791
2791
|
* @param {string} [path]
|
|
2792
2792
|
* @return {(string|null|Command)}
|
|
2793
2793
|
*/
|
|
2794
|
-
executableDir(
|
|
2795
|
-
if (
|
|
2796
|
-
this._executableDir =
|
|
2794
|
+
executableDir(path18) {
|
|
2795
|
+
if (path18 === void 0) return this._executableDir;
|
|
2796
|
+
this._executableDir = path18;
|
|
2797
2797
|
return this;
|
|
2798
2798
|
}
|
|
2799
2799
|
/**
|
|
@@ -3026,9 +3026,9 @@ var require_commander = __commonJS({
|
|
|
3026
3026
|
// ../../node_modules/xmlhttprequest-ssl/lib/XMLHttpRequest.js
|
|
3027
3027
|
var require_XMLHttpRequest = __commonJS({
|
|
3028
3028
|
"../../node_modules/xmlhttprequest-ssl/lib/XMLHttpRequest.js"(exports2, module2) {
|
|
3029
|
-
var
|
|
3029
|
+
var fs14 = require("fs");
|
|
3030
3030
|
var Url = require("url");
|
|
3031
|
-
var
|
|
3031
|
+
var spawn5 = require("child_process").spawn;
|
|
3032
3032
|
module2.exports = XMLHttpRequest3;
|
|
3033
3033
|
XMLHttpRequest3.XMLHttpRequest = XMLHttpRequest3;
|
|
3034
3034
|
function XMLHttpRequest3(opts) {
|
|
@@ -3184,7 +3184,7 @@ var require_XMLHttpRequest = __commonJS({
|
|
|
3184
3184
|
throw new Error("XMLHttpRequest: Only GET method is supported");
|
|
3185
3185
|
}
|
|
3186
3186
|
if (settings.async) {
|
|
3187
|
-
|
|
3187
|
+
fs14.readFile(unescape(url2.pathname), function(error, data2) {
|
|
3188
3188
|
if (error) {
|
|
3189
3189
|
self.handleError(error, error.errno || -1);
|
|
3190
3190
|
} else {
|
|
@@ -3196,7 +3196,7 @@ var require_XMLHttpRequest = __commonJS({
|
|
|
3196
3196
|
});
|
|
3197
3197
|
} else {
|
|
3198
3198
|
try {
|
|
3199
|
-
this.response =
|
|
3199
|
+
this.response = fs14.readFileSync(unescape(url2.pathname));
|
|
3200
3200
|
this.responseText = this.response.toString("utf8");
|
|
3201
3201
|
this.status = 200;
|
|
3202
3202
|
setState(self.DONE);
|
|
@@ -3322,15 +3322,15 @@ var require_XMLHttpRequest = __commonJS({
|
|
|
3322
3322
|
} else {
|
|
3323
3323
|
var contentFile = ".node-xmlhttprequest-content-" + process.pid;
|
|
3324
3324
|
var syncFile = ".node-xmlhttprequest-sync-" + process.pid;
|
|
3325
|
-
|
|
3325
|
+
fs14.writeFileSync(syncFile, "", "utf8");
|
|
3326
3326
|
var execString = "var http = require('http'), https = require('https'), fs = require('fs');var doRequest = http" + (ssl ? "s" : "") + ".request;var options = " + JSON.stringify(options) + ";var responseText = '';var responseData = Buffer.alloc(0);var req = doRequest(options, function(response) {response.on('data', function(chunk) { var data = Buffer.from(chunk); responseText += data.toString('utf8'); responseData = Buffer.concat([responseData, data]);});response.on('end', function() {fs.writeFileSync('" + contentFile + "', JSON.stringify({err: null, data: {statusCode: response.statusCode, headers: response.headers, text: responseText, data: responseData.toString('base64')}}), 'utf8');fs.unlinkSync('" + syncFile + "');});response.on('error', function(error) {fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-ERROR:' + JSON.stringify(error), 'utf8');fs.unlinkSync('" + syncFile + "');});}).on('error', function(error) {fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-ERROR:' + JSON.stringify(error), 'utf8');fs.unlinkSync('" + syncFile + "');});" + (data ? "req.write('" + JSON.stringify(data).slice(1, -1).replace(/'/g, "\\'") + "');" : "") + "req.end();";
|
|
3327
|
-
var syncProc =
|
|
3327
|
+
var syncProc = spawn5(process.argv[0], ["-e", execString]);
|
|
3328
3328
|
var statusText;
|
|
3329
|
-
while (
|
|
3329
|
+
while (fs14.existsSync(syncFile)) {
|
|
3330
3330
|
}
|
|
3331
|
-
self.responseText =
|
|
3331
|
+
self.responseText = fs14.readFileSync(contentFile, "utf8");
|
|
3332
3332
|
syncProc.stdin.end();
|
|
3333
|
-
|
|
3333
|
+
fs14.unlinkSync(contentFile);
|
|
3334
3334
|
if (self.responseText.match(/^NODE-XMLHTTPREQUEST-ERROR:/)) {
|
|
3335
3335
|
var errorObj = JSON.parse(self.responseText.replace(/^NODE-XMLHTTPREQUEST-ERROR:/, ""));
|
|
3336
3336
|
self.handleError(errorObj, 503);
|
|
@@ -3984,7 +3984,7 @@ var require_has_flag = __commonJS({
|
|
|
3984
3984
|
var require_supports_color = __commonJS({
|
|
3985
3985
|
"../../node_modules/debug/node_modules/supports-color/index.js"(exports2, module2) {
|
|
3986
3986
|
"use strict";
|
|
3987
|
-
var
|
|
3987
|
+
var os6 = require("os");
|
|
3988
3988
|
var hasFlag = require_has_flag();
|
|
3989
3989
|
var env = process.env;
|
|
3990
3990
|
var forceColor;
|
|
@@ -4022,7 +4022,7 @@ var require_supports_color = __commonJS({
|
|
|
4022
4022
|
}
|
|
4023
4023
|
const min = forceColor ? 1 : 0;
|
|
4024
4024
|
if (process.platform === "win32") {
|
|
4025
|
-
const osRelease =
|
|
4025
|
+
const osRelease = os6.release().split(".");
|
|
4026
4026
|
if (Number(process.versions.node.split(".")[0]) >= 8 && Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
|
4027
4027
|
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
4028
4028
|
}
|
|
@@ -8428,8 +8428,8 @@ function getErrorMap() {
|
|
|
8428
8428
|
|
|
8429
8429
|
// ../../node_modules/zod/v3/helpers/parseUtil.js
|
|
8430
8430
|
var makeIssue = (params) => {
|
|
8431
|
-
const { data, path:
|
|
8432
|
-
const fullPath = [...
|
|
8431
|
+
const { data, path: path17, errorMaps, issueData } = params;
|
|
8432
|
+
const fullPath = [...path17, ...issueData.path || []];
|
|
8433
8433
|
const fullIssue = {
|
|
8434
8434
|
...issueData,
|
|
8435
8435
|
path: fullPath
|
|
@@ -8545,11 +8545,11 @@ var errorUtil;
|
|
|
8545
8545
|
|
|
8546
8546
|
// ../../node_modules/zod/v3/types.js
|
|
8547
8547
|
var ParseInputLazyPath = class {
|
|
8548
|
-
constructor(parent, value2,
|
|
8548
|
+
constructor(parent, value2, path17, key) {
|
|
8549
8549
|
this._cachedPath = [];
|
|
8550
8550
|
this.parent = parent;
|
|
8551
8551
|
this.data = value2;
|
|
8552
|
-
this._path =
|
|
8552
|
+
this._path = path17;
|
|
8553
8553
|
this._key = key;
|
|
8554
8554
|
}
|
|
8555
8555
|
get path() {
|
|
@@ -12908,7 +12908,7 @@ ${diff.slice(0, 8e3)}`,
|
|
|
12908
12908
|
}
|
|
12909
12909
|
|
|
12910
12910
|
// ../../shared/all/helpers/board-agent-core/engine.ts
|
|
12911
|
-
var
|
|
12911
|
+
var import_path9 = require("path");
|
|
12912
12912
|
|
|
12913
12913
|
// ../../shared/all/domains/electron.ts
|
|
12914
12914
|
var SETUP_LOG_PREFIX = "log ";
|
|
@@ -12922,6 +12922,7 @@ function hostSpacePaths(spaceDir) {
|
|
|
12922
12922
|
repo: import_path.default.join(spaceDir, "repo"),
|
|
12923
12923
|
worktrees: import_path.default.join(spaceDir, "worktrees"),
|
|
12924
12924
|
workspace: import_path.default.join(spaceDir, "workspace"),
|
|
12925
|
+
skills: import_path.default.join(spaceDir, "skills"),
|
|
12925
12926
|
state: import_path.default.join(spaceDir, "state"),
|
|
12926
12927
|
backups: import_path.default.join(spaceDir, "backups"),
|
|
12927
12928
|
builds: import_path.default.join(spaceDir, "builds"),
|
|
@@ -12951,9 +12952,23 @@ function buildCloneUrl(repoUrl, githubToken) {
|
|
|
12951
12952
|
async function gitInDir(target, workdir, ...args) {
|
|
12952
12953
|
return target.run(["git", "-C", workdir, ...args]);
|
|
12953
12954
|
}
|
|
12954
|
-
var
|
|
12955
|
-
|
|
12956
|
-
|
|
12955
|
+
var WORK_BRANCH_PREFIXES = {
|
|
12956
|
+
card: "card/",
|
|
12957
|
+
terminal: "terminal/"
|
|
12958
|
+
};
|
|
12959
|
+
var LEGACY_WORK_BRANCH_PREFIX = "factiii/card-";
|
|
12960
|
+
var ALL_WORK_BRANCH_PREFIXES = [
|
|
12961
|
+
...Object.values(WORK_BRANCH_PREFIXES),
|
|
12962
|
+
LEGACY_WORK_BRANCH_PREFIX
|
|
12963
|
+
].sort((a, b) => b.length - a.length);
|
|
12964
|
+
function workBranch(workName, kind) {
|
|
12965
|
+
return `${WORK_BRANCH_PREFIXES[kind]}${workName.replace(/[^a-zA-Z0-9._-]/g, "-")}`;
|
|
12966
|
+
}
|
|
12967
|
+
function workNameFromBranch(branch) {
|
|
12968
|
+
for (const prefix of ALL_WORK_BRANCH_PREFIXES) {
|
|
12969
|
+
if (branch.startsWith(prefix)) return branch.slice(prefix.length);
|
|
12970
|
+
}
|
|
12971
|
+
return null;
|
|
12957
12972
|
}
|
|
12958
12973
|
async function listWorkNames(target) {
|
|
12959
12974
|
const dirs = await target.sh(`ls -1 ${target.paths.worktrees} 2>/dev/null || true`).catch(() => "");
|
|
@@ -12966,10 +12981,8 @@ async function listWorkNames(target) {
|
|
|
12966
12981
|
).catch(() => "");
|
|
12967
12982
|
const names = new Set(dirs.split("\n").map((line) => line.trim()));
|
|
12968
12983
|
for (const line of branches.split("\n")) {
|
|
12969
|
-
const
|
|
12970
|
-
if (
|
|
12971
|
-
names.add(name.slice(WORK_BRANCH_PREFIX.length));
|
|
12972
|
-
}
|
|
12984
|
+
const workName = workNameFromBranch(line.trim());
|
|
12985
|
+
if (workName) names.add(workName);
|
|
12973
12986
|
}
|
|
12974
12987
|
names.delete("");
|
|
12975
12988
|
return [...names];
|
|
@@ -13003,10 +13016,18 @@ async function ensurePrimaryClone(target, cloneUrl, branch) {
|
|
|
13003
13016
|
});
|
|
13004
13017
|
await target.sh(`mkdir -p ${target.paths.worktrees}`);
|
|
13005
13018
|
}
|
|
13006
|
-
async function addWorktree(target, workName, baseBranch) {
|
|
13007
|
-
const
|
|
13008
|
-
const branch =
|
|
13009
|
-
await
|
|
13019
|
+
async function addWorktree(target, workName, baseBranch, kind) {
|
|
13020
|
+
const path17 = worktreeFor(target.paths, workName);
|
|
13021
|
+
const branch = workBranch(workName, kind);
|
|
13022
|
+
await gitInDir(
|
|
13023
|
+
target,
|
|
13024
|
+
target.paths.repo,
|
|
13025
|
+
"fetch",
|
|
13026
|
+
"origin",
|
|
13027
|
+
baseBranch
|
|
13028
|
+
).catch(() => {
|
|
13029
|
+
});
|
|
13030
|
+
await removeWorktree(target, path17).catch(() => {
|
|
13010
13031
|
});
|
|
13011
13032
|
await gitInDir(
|
|
13012
13033
|
target,
|
|
@@ -13015,29 +13036,45 @@ async function addWorktree(target, workName, baseBranch) {
|
|
|
13015
13036
|
"add",
|
|
13016
13037
|
"-B",
|
|
13017
13038
|
branch,
|
|
13018
|
-
|
|
13039
|
+
path17,
|
|
13019
13040
|
`origin/${baseBranch}`
|
|
13020
13041
|
);
|
|
13021
|
-
return { path:
|
|
13042
|
+
return { path: path17, branch };
|
|
13022
13043
|
}
|
|
13023
|
-
async function removeWorktree(target,
|
|
13044
|
+
async function removeWorktree(target, path17) {
|
|
13024
13045
|
await gitInDir(
|
|
13025
13046
|
target,
|
|
13026
13047
|
target.paths.repo,
|
|
13027
13048
|
"worktree",
|
|
13028
13049
|
"remove",
|
|
13029
13050
|
"--force",
|
|
13030
|
-
|
|
13051
|
+
path17
|
|
13031
13052
|
);
|
|
13032
13053
|
}
|
|
13054
|
+
async function removeWorktreeAndBranch(target, path17) {
|
|
13055
|
+
const branch = await gitInDir(
|
|
13056
|
+
target,
|
|
13057
|
+
path17,
|
|
13058
|
+
"rev-parse",
|
|
13059
|
+
"--abbrev-ref",
|
|
13060
|
+
"HEAD"
|
|
13061
|
+
).then((out) => out.trim()).catch(() => "");
|
|
13062
|
+
await removeWorktree(target, path17);
|
|
13063
|
+
if (branch && branch !== "HEAD") {
|
|
13064
|
+
await gitInDir(target, target.paths.repo, "branch", "-D", branch).catch(
|
|
13065
|
+
() => {
|
|
13066
|
+
}
|
|
13067
|
+
);
|
|
13068
|
+
}
|
|
13069
|
+
}
|
|
13033
13070
|
async function pruneWorktrees(target) {
|
|
13034
13071
|
await gitInDir(target, target.paths.repo, "worktree", "prune").catch(
|
|
13035
13072
|
() => {
|
|
13036
13073
|
}
|
|
13037
13074
|
);
|
|
13038
13075
|
}
|
|
13039
|
-
function isWorktreePath(target,
|
|
13040
|
-
return
|
|
13076
|
+
function isWorktreePath(target, path17) {
|
|
13077
|
+
return path17.startsWith(`${target.paths.worktrees}/`);
|
|
13041
13078
|
}
|
|
13042
13079
|
async function setGitIdentity(target, name, email, workdir = target.paths.workspace) {
|
|
13043
13080
|
if (name) await gitInDir(target, workdir, "config", "user.name", name);
|
|
@@ -13052,6 +13089,147 @@ async function extractDiff(target, workdir = target.paths.workspace) {
|
|
|
13052
13089
|
changedFiles: changedFilesRaw ? changedFilesRaw.split("\n") : []
|
|
13053
13090
|
};
|
|
13054
13091
|
}
|
|
13092
|
+
var LOG_FORMAT = "%H%x1f%h%x1f%an%x1f%aI%x1f%ar%x1f%s";
|
|
13093
|
+
var LOG_FIELD_SEP = "";
|
|
13094
|
+
function parseStatus(raw) {
|
|
13095
|
+
const staged = [];
|
|
13096
|
+
const unstaged = [];
|
|
13097
|
+
for (const line of raw.split("\n")) {
|
|
13098
|
+
if (line.length < 4) continue;
|
|
13099
|
+
const x = line[0];
|
|
13100
|
+
const y = line[1];
|
|
13101
|
+
const rest = line.slice(3);
|
|
13102
|
+
const arrow = rest.indexOf(" -> ");
|
|
13103
|
+
const path17 = arrow === -1 ? rest : rest.slice(arrow + 4);
|
|
13104
|
+
if (x === "?" || y === "?") {
|
|
13105
|
+
unstaged.push({ path: path17, code: "A" });
|
|
13106
|
+
continue;
|
|
13107
|
+
}
|
|
13108
|
+
if (x !== " ") staged.push({ path: path17, code: x });
|
|
13109
|
+
if (y !== " ") unstaged.push({ path: path17, code: y });
|
|
13110
|
+
}
|
|
13111
|
+
return { staged, unstaged };
|
|
13112
|
+
}
|
|
13113
|
+
function parseLog(raw, localShas) {
|
|
13114
|
+
const commits = [];
|
|
13115
|
+
for (const line of raw.split("\n")) {
|
|
13116
|
+
if (!line.trim()) continue;
|
|
13117
|
+
const [sha, shortSha, author, date, relative, ...subject] = line.split(LOG_FIELD_SEP);
|
|
13118
|
+
if (!sha || !shortSha) continue;
|
|
13119
|
+
commits.push({
|
|
13120
|
+
sha,
|
|
13121
|
+
shortSha,
|
|
13122
|
+
author: author ?? "",
|
|
13123
|
+
date: date ?? "",
|
|
13124
|
+
relative: relative ?? "",
|
|
13125
|
+
// A subject holding the separator would split into extra fields; joining
|
|
13126
|
+
// the tail back puts it together again.
|
|
13127
|
+
subject: subject.join(LOG_FIELD_SEP) || "(no commit message)",
|
|
13128
|
+
local: localShas.has(sha)
|
|
13129
|
+
});
|
|
13130
|
+
}
|
|
13131
|
+
return commits;
|
|
13132
|
+
}
|
|
13133
|
+
async function readGitPanel(target, workdir, logLimit = 40) {
|
|
13134
|
+
const git = (...args) => gitInDir(target, workdir, ...args).catch(() => "");
|
|
13135
|
+
await git("add", "--intent-to-add", ".");
|
|
13136
|
+
const { staged, unstaged } = parseStatus(
|
|
13137
|
+
await git(
|
|
13138
|
+
"--no-optional-locks",
|
|
13139
|
+
"-c",
|
|
13140
|
+
"core.quotePath=false",
|
|
13141
|
+
"status",
|
|
13142
|
+
"--porcelain=v1",
|
|
13143
|
+
"--untracked-files=all"
|
|
13144
|
+
)
|
|
13145
|
+
);
|
|
13146
|
+
const stagedDiff = await git("diff", "--cached", "--no-color");
|
|
13147
|
+
const unstagedDiff = await git("diff", "--no-color");
|
|
13148
|
+
const diff = await git("diff", "HEAD", "--no-color");
|
|
13149
|
+
const localShas = new Set(
|
|
13150
|
+
(await git(
|
|
13151
|
+
"rev-list",
|
|
13152
|
+
"-n",
|
|
13153
|
+
String(logLimit),
|
|
13154
|
+
"HEAD",
|
|
13155
|
+
"--not",
|
|
13156
|
+
"--remotes"
|
|
13157
|
+
)).split("\n").map((sha) => sha.trim()).filter(Boolean)
|
|
13158
|
+
);
|
|
13159
|
+
const commits = parseLog(
|
|
13160
|
+
await git("log", "-n", String(logLimit), `--format=${LOG_FORMAT}`),
|
|
13161
|
+
localShas
|
|
13162
|
+
);
|
|
13163
|
+
const branch = (await git("rev-parse", "--abbrev-ref", "HEAD")).trim();
|
|
13164
|
+
return {
|
|
13165
|
+
branch,
|
|
13166
|
+
staged: { files: staged, diff: stagedDiff },
|
|
13167
|
+
unstaged: { files: unstaged, diff: unstagedDiff },
|
|
13168
|
+
commits,
|
|
13169
|
+
diff,
|
|
13170
|
+
// The union of both sides, which is what `git diff HEAD --name-only`
|
|
13171
|
+
// reports - one call cheaper.
|
|
13172
|
+
changedFiles: [
|
|
13173
|
+
...new Set([...staged, ...unstaged].map((file) => file.path))
|
|
13174
|
+
]
|
|
13175
|
+
};
|
|
13176
|
+
}
|
|
13177
|
+
function toRepoRelative(paths) {
|
|
13178
|
+
const clean = [];
|
|
13179
|
+
for (const raw of paths) {
|
|
13180
|
+
const rel = raw.trim().replace(/^\/+/, "");
|
|
13181
|
+
if (!rel || rel === ".") continue;
|
|
13182
|
+
if (rel.split("/").includes("..")) continue;
|
|
13183
|
+
clean.push(rel);
|
|
13184
|
+
}
|
|
13185
|
+
return clean;
|
|
13186
|
+
}
|
|
13187
|
+
function stageTargets(paths) {
|
|
13188
|
+
if (paths.length === 0) return ["."];
|
|
13189
|
+
const rel = toRepoRelative(paths);
|
|
13190
|
+
if (rel.length === 0) throw new Error("No path in that request was usable.");
|
|
13191
|
+
return rel;
|
|
13192
|
+
}
|
|
13193
|
+
async function stagePaths(target, workdir, paths) {
|
|
13194
|
+
await gitInDir(target, workdir, "add", "--", ...stageTargets(paths));
|
|
13195
|
+
}
|
|
13196
|
+
async function unstagePaths(target, workdir, paths) {
|
|
13197
|
+
const targets = stageTargets(paths);
|
|
13198
|
+
try {
|
|
13199
|
+
await gitInDir(target, workdir, "restore", "--staged", "--", ...targets);
|
|
13200
|
+
} catch {
|
|
13201
|
+
await gitInDir(
|
|
13202
|
+
target,
|
|
13203
|
+
workdir,
|
|
13204
|
+
"rm",
|
|
13205
|
+
"--cached",
|
|
13206
|
+
"-r",
|
|
13207
|
+
"--quiet",
|
|
13208
|
+
"--",
|
|
13209
|
+
...targets
|
|
13210
|
+
);
|
|
13211
|
+
}
|
|
13212
|
+
}
|
|
13213
|
+
async function readCommitDiff(target, workdir, sha) {
|
|
13214
|
+
if (!/^[0-9a-f]{4,40}$/i.test(sha)) {
|
|
13215
|
+
throw new Error(`Not a commit id: ${sha}`);
|
|
13216
|
+
}
|
|
13217
|
+
const show = (...args) => gitInDir(
|
|
13218
|
+
target,
|
|
13219
|
+
workdir,
|
|
13220
|
+
"show",
|
|
13221
|
+
"--first-parent",
|
|
13222
|
+
"-m",
|
|
13223
|
+
...args,
|
|
13224
|
+
sha
|
|
13225
|
+
).catch(() => "");
|
|
13226
|
+
const diff = await show("--no-color", "--format=", "--patch");
|
|
13227
|
+
const names = (await show("--format=", "--name-only")).trim();
|
|
13228
|
+
return {
|
|
13229
|
+
diff,
|
|
13230
|
+
changedFiles: names ? names.split("\n").filter(Boolean) : []
|
|
13231
|
+
};
|
|
13232
|
+
}
|
|
13055
13233
|
async function listRemoteBranches(target, workdir = target.paths.workspace) {
|
|
13056
13234
|
try {
|
|
13057
13235
|
const stdout = await gitInDir(
|
|
@@ -13218,7 +13396,8 @@ async function putSessionCard(s, target, prompt2 = "") {
|
|
|
13218
13396
|
}
|
|
13219
13397
|
async function teardownSession(s, target) {
|
|
13220
13398
|
if (isWorktreeSession(s, target)) {
|
|
13221
|
-
|
|
13399
|
+
const drop = s.mode === "bare" ? removeWorktreeAndBranch : removeWorktree;
|
|
13400
|
+
await drop(target, s.workspacePath).catch(() => {
|
|
13222
13401
|
});
|
|
13223
13402
|
}
|
|
13224
13403
|
await deleteCard(target, s.postId).catch(() => {
|
|
@@ -13665,116 +13844,1789 @@ function createEventSink(send, spaceSlug, notify) {
|
|
|
13665
13844
|
|
|
13666
13845
|
// ../../shared/all/helpers/card-ai/frpc.ts
|
|
13667
13846
|
var import_fs2 = require("fs");
|
|
13668
|
-
var
|
|
13669
|
-
var
|
|
13847
|
+
var import_promises4 = __toESM(require("fs/promises"));
|
|
13848
|
+
var import_path6 = __toESM(require("path"));
|
|
13670
13849
|
|
|
13671
|
-
// ../../shared/all/helpers/
|
|
13672
|
-
|
|
13673
|
-
|
|
13674
|
-
|
|
13675
|
-
set -a; . '${p.envFile}' 2>/dev/null || true; set +a
|
|
13676
|
-
mkdir -p '${p.runDir}'
|
|
13677
|
-
PORT="$1"
|
|
13678
|
-
NAME="$2"
|
|
13679
|
-
if [ -z "$PORT" ] || [ -z "$NAME" ]; then
|
|
13680
|
-
echo "usage: factiii-expose <port> <name>" >&2
|
|
13681
|
-
echo " e.g. factiii-expose 3000 app" >&2
|
|
13682
|
-
exit 1
|
|
13683
|
-
fi
|
|
13684
|
-
case "$NAME" in *[!a-zA-Z0-9-]*|"") echo "name must be [a-zA-Z0-9-]" >&2; exit 1 ;; esac
|
|
13685
|
-
PROXY="\${FACTIII_TASK_ID}-\${NAME}"
|
|
13686
|
-
DOMAIN="\${PROXY}.\${FACTIII_FRP_DOMAIN}"
|
|
13687
|
-
TOML="${p.runDir}/\${NAME}.toml"
|
|
13688
|
-
cat > "$TOML" <<EOF
|
|
13689
|
-
serverAddr = "\${FACTIII_FRP_SERVER}"
|
|
13690
|
-
serverPort = \${FACTIII_FRP_PORT}
|
|
13691
|
-
transport.protocol = "tcp"
|
|
13850
|
+
// ../../shared/all/helpers/board-agent-core/host-skills.ts
|
|
13851
|
+
var import_promises3 = __toESM(require("fs/promises"));
|
|
13852
|
+
var import_os2 = __toESM(require("os"));
|
|
13853
|
+
var import_path5 = __toESM(require("path"));
|
|
13692
13854
|
|
|
13693
|
-
|
|
13694
|
-
|
|
13855
|
+
// ../../shared/all/domains/posts.ts
|
|
13856
|
+
var postDataSort = /* @__PURE__ */ ((postDataSort2) => {
|
|
13857
|
+
postDataSort2["DAILY"] = "DAILY";
|
|
13858
|
+
postDataSort2["WEEKLY"] = "WEEKLY";
|
|
13859
|
+
postDataSort2["MONTHLY"] = "MONTHLY";
|
|
13860
|
+
return postDataSort2;
|
|
13861
|
+
})(postDataSort || {});
|
|
13695
13862
|
|
|
13696
|
-
|
|
13697
|
-
|
|
13698
|
-
|
|
13699
|
-
|
|
13700
|
-
|
|
13701
|
-
|
|
13702
|
-
|
|
13703
|
-
|
|
13704
|
-
|
|
13705
|
-
|
|
13706
|
-
|
|
13707
|
-
|
|
13708
|
-
|
|
13709
|
-
|
|
13710
|
-
|
|
13711
|
-
|
|
13712
|
-
|
|
13713
|
-
|
|
13714
|
-
|
|
13715
|
-
|
|
13716
|
-
|
|
13717
|
-
|
|
13718
|
-
|
|
13719
|
-
|
|
13720
|
-
|
|
13721
|
-
|
|
13722
|
-
|
|
13723
|
-
|
|
13724
|
-
|
|
13725
|
-
|
|
13863
|
+
// ../../shared/all/domains/user.ts
|
|
13864
|
+
var USER_TAG_VALUES = [
|
|
13865
|
+
"HUMAN",
|
|
13866
|
+
"BOT",
|
|
13867
|
+
"GOVERNMENT",
|
|
13868
|
+
"ACADEMIA",
|
|
13869
|
+
"BUSINESS",
|
|
13870
|
+
"NEW",
|
|
13871
|
+
"WAITLIST",
|
|
13872
|
+
"VERIFIED"
|
|
13873
|
+
];
|
|
13874
|
+
var detailsCookieSchema = external_exports.object({
|
|
13875
|
+
userId: external_exports.number(),
|
|
13876
|
+
updatedAt: external_exports.string(),
|
|
13877
|
+
username: external_exports.string(),
|
|
13878
|
+
name: external_exports.string().nullable(),
|
|
13879
|
+
email: external_exports.string().nullable(),
|
|
13880
|
+
tag: external_exports.enum(USER_TAG_VALUES),
|
|
13881
|
+
avatarKey: external_exports.string().nullable(),
|
|
13882
|
+
robohash: external_exports.string().nullable(),
|
|
13883
|
+
hasPassword: external_exports.boolean(),
|
|
13884
|
+
// Defaulted so a client cookie minted before these fields existed still parses
|
|
13885
|
+
// (the server re-issues it with real values on the next refresh) instead of
|
|
13886
|
+
// failing the whole payload and flashing the user as logged out.
|
|
13887
|
+
hasPasskey: external_exports.boolean().default(false),
|
|
13888
|
+
twoFaEnabled: external_exports.boolean(),
|
|
13889
|
+
oauthProviders: external_exports.array(external_exports.enum(["GOOGLE", "APPLE"])).default([]),
|
|
13890
|
+
// Defaulted, not just nullable: a required-but-missing key is what breaks old
|
|
13891
|
+
// clients, so this stays parseable once the server stops sending it.
|
|
13892
|
+
oauthProvider: external_exports.enum(["GOOGLE", "APPLE"]).nullable().default(null),
|
|
13893
|
+
emailVerificationStatus: external_exports.enum(["UNVERIFIED", "PENDING", "VERIFIED"]),
|
|
13894
|
+
isPrivate: external_exports.boolean(),
|
|
13895
|
+
isIncognito: external_exports.boolean(),
|
|
13896
|
+
onboardingPath: external_exports.string().nullable(),
|
|
13897
|
+
bio: external_exports.string().nullable(),
|
|
13898
|
+
bannerKey: external_exports.string().nullable(),
|
|
13899
|
+
tronsBalance: external_exports.number(),
|
|
13900
|
+
verificationRequestedAt: external_exports.string().nullable(),
|
|
13901
|
+
deletionScheduledAt: external_exports.string().nullable()
|
|
13902
|
+
});
|
|
13903
|
+
var UserType = /* @__PURE__ */ ((UserType2) => {
|
|
13904
|
+
UserType2["PRIVATE"] = "PRIVATE";
|
|
13905
|
+
UserType2["INCOGNITO"] = "INCOGNITO";
|
|
13906
|
+
UserType2["PREMIUM"] = "PREMIUM";
|
|
13907
|
+
UserType2["PAID"] = "PAID";
|
|
13908
|
+
UserType2["INVITE"] = "INVITE";
|
|
13909
|
+
return UserType2;
|
|
13910
|
+
})(UserType || {});
|
|
13726
13911
|
|
|
13727
|
-
// ../../shared/all/
|
|
13728
|
-
var
|
|
13729
|
-
|
|
13730
|
-
|
|
13731
|
-
|
|
13732
|
-
|
|
13912
|
+
// ../../shared/all/uploadTypes.ts
|
|
13913
|
+
var _EXT_TO_CONTENT_TYPE = {
|
|
13914
|
+
jpg: "image/jpeg",
|
|
13915
|
+
jpeg: "image/jpeg",
|
|
13916
|
+
png: "image/png",
|
|
13917
|
+
gif: "image/gif",
|
|
13918
|
+
webp: "image/webp",
|
|
13919
|
+
svg: "image/svg+xml",
|
|
13920
|
+
heic: "image/heic",
|
|
13921
|
+
heif: "image/heif",
|
|
13922
|
+
mp4: "video/mp4",
|
|
13923
|
+
mov: "video/quicktime",
|
|
13924
|
+
mpeg: "video/mpeg",
|
|
13925
|
+
pdf: "application/pdf",
|
|
13926
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
13927
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
13928
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
13929
|
+
};
|
|
13930
|
+
var EXT_TO_CONTENT_TYPE = _EXT_TO_CONTENT_TYPE;
|
|
13931
|
+
var ALLOWED_CONTENT_TYPES = [
|
|
13932
|
+
...new Set(Object.values(EXT_TO_CONTENT_TYPE))
|
|
13933
|
+
];
|
|
13934
|
+
var ALLOWED_CONTENT_TYPES_TUPLE = ALLOWED_CONTENT_TYPES;
|
|
13935
|
+
var CONTENT_TYPE_TO_EXT = Object.fromEntries(
|
|
13936
|
+
Object.entries(EXT_TO_CONTENT_TYPE).map(([ext, mime]) => [mime, ext])
|
|
13937
|
+
);
|
|
13938
|
+
var DROPZONE_ACCEPT = Object.entries(
|
|
13939
|
+
EXT_TO_CONTENT_TYPE
|
|
13940
|
+
).reduce((acc, [ext, mime]) => {
|
|
13941
|
+
(acc[mime] ??= []).push(`.${ext}`);
|
|
13942
|
+
return acc;
|
|
13943
|
+
}, {});
|
|
13944
|
+
var FILE_INPUT_ACCEPT = ALLOWED_CONTENT_TYPES.join(",");
|
|
13945
|
+
|
|
13946
|
+
// ../../shared/all/validators.ts
|
|
13947
|
+
var usernameValidationRegex = /^[a-zA-Z0-9_]+$/;
|
|
13948
|
+
var signupSchema = external_exports.object({
|
|
13949
|
+
username: external_exports.string().min(1).regex(usernameValidationRegex, {
|
|
13950
|
+
message: "Username can only contain letters, numbers, and underscores"
|
|
13951
|
+
}),
|
|
13952
|
+
email: external_exports.string().email(),
|
|
13953
|
+
password: external_exports.string().min(8, { message: "Password must contain at least 8 characters" }),
|
|
13954
|
+
types: external_exports.array(external_exports.nativeEnum(UserType)),
|
|
13955
|
+
referrerUserId: external_exports.number().optional(),
|
|
13956
|
+
instanceId: external_exports.number()
|
|
13957
|
+
});
|
|
13958
|
+
var usernameSchema = external_exports.string().min(1).regex(usernameValidationRegex, {
|
|
13959
|
+
message: "Username can only contain letters, numbers, and underscores"
|
|
13960
|
+
});
|
|
13961
|
+
var baseRegisterSchema = external_exports.object({
|
|
13962
|
+
username: usernameSchema,
|
|
13963
|
+
types: external_exports.array(external_exports.nativeEnum(UserType)).default([]),
|
|
13964
|
+
referrerUserId: external_exports.number().optional(),
|
|
13965
|
+
instanceId: external_exports.number(),
|
|
13966
|
+
platform: external_exports.enum(["web", "mobile"]).optional()
|
|
13967
|
+
});
|
|
13968
|
+
var passwordRegisterSchema = baseRegisterSchema.extend({
|
|
13969
|
+
password: external_exports.string().min(8, { message: "Password must contain at least 8 characters" }),
|
|
13970
|
+
recaptchaToken: external_exports.string().optional()
|
|
13971
|
+
});
|
|
13972
|
+
var createSourceSchema = external_exports.object({
|
|
13973
|
+
name: external_exports.string().min(1),
|
|
13974
|
+
description: external_exports.string().optional(),
|
|
13975
|
+
requirements: external_exports.array(
|
|
13976
|
+
external_exports.enum([
|
|
13977
|
+
"GOVERNMENT_SOURCE" /* GOVERNMENT_SOURCE */,
|
|
13978
|
+
"ENTERPRISE_SOURCE" /* ENTERPRISE_SOURCE */,
|
|
13979
|
+
"ANONYMOUS_SOURCE" /* ANONYMOUS_SOURCE */,
|
|
13980
|
+
"HUMAN_SOURCE" /* HUMAN_SOURCE */
|
|
13981
|
+
])
|
|
13982
|
+
)
|
|
13983
|
+
});
|
|
13984
|
+
var contactFormSchema = external_exports.object({
|
|
13985
|
+
name: external_exports.string().min(1).max(100),
|
|
13986
|
+
email: external_exports.string().max(254).optional(),
|
|
13987
|
+
phone: external_exports.string().max(20).optional(),
|
|
13988
|
+
message: external_exports.string().min(1).max(2e3),
|
|
13989
|
+
age: external_exports.string().optional()
|
|
13990
|
+
}).refine((data) => !!data.email || !!data.phone, {
|
|
13991
|
+
message: "Either email or phone is required"
|
|
13992
|
+
});
|
|
13993
|
+
var donationSchema = external_exports.object({
|
|
13994
|
+
amount: external_exports.number().int().min(100).max(1e6),
|
|
13995
|
+
// Amount in cents (integer)
|
|
13996
|
+
email: external_exports.string().email()
|
|
13997
|
+
});
|
|
13998
|
+
var getUploadUrlsSchema = external_exports.object({
|
|
13999
|
+
contentType: external_exports.enum(ALLOWED_CONTENT_TYPES_TUPLE),
|
|
14000
|
+
name: external_exports.string().optional(),
|
|
14001
|
+
width: external_exports.number().int().positive().max(25e3).optional(),
|
|
14002
|
+
height: external_exports.number().int().positive().max(25e3).optional(),
|
|
14003
|
+
isPrivate: external_exports.boolean().optional().default(false),
|
|
14004
|
+
// Chat attachments pass the conversation instead of an isPrivate flag: the
|
|
14005
|
+
// server resolves privacy from the channel's space so the client can't
|
|
14006
|
+
// decide to make a PRIVATE space's files public. See uploads.getUploadUrls.
|
|
14007
|
+
conversationId: external_exports.string().uuid().optional(),
|
|
14008
|
+
isFactiii: external_exports.boolean().default(false),
|
|
14009
|
+
factiiiId: external_exports.number().int().positive().optional(),
|
|
14010
|
+
rawSha256: external_exports.string().regex(/^[a-f0-9]{64}$/i, "rawSha256 must be 64 hex chars")
|
|
14011
|
+
}).refine(
|
|
14012
|
+
(d) => d.width === void 0 && d.height === void 0 || d.width !== void 0 && d.height !== void 0,
|
|
14013
|
+
{ message: "width and height must be provided together" }
|
|
14014
|
+
).refine((d) => !d.isFactiii || d.factiiiId !== void 0, {
|
|
14015
|
+
message: "factiiiId is required when isFactiii is true"
|
|
14016
|
+
});
|
|
14017
|
+
var finalizeUploadSchema = external_exports.object({
|
|
14018
|
+
uploadId: external_exports.string(),
|
|
14019
|
+
isFactiii: external_exports.boolean(),
|
|
14020
|
+
raw: external_exports.object({
|
|
14021
|
+
// Bare-uuid keys; variant kind is the DB-level discriminator, no S3 prefix.
|
|
14022
|
+
key: external_exports.string().min(1),
|
|
14023
|
+
size: external_exports.number().int().positive(),
|
|
14024
|
+
sha256: external_exports.string().regex(/^[a-f0-9]{64}$/i),
|
|
14025
|
+
width: external_exports.number().int().positive().optional(),
|
|
14026
|
+
height: external_exports.number().int().positive().optional()
|
|
14027
|
+
}),
|
|
14028
|
+
view: external_exports.object({
|
|
14029
|
+
key: external_exports.string().min(1),
|
|
14030
|
+
size: external_exports.number().int().positive(),
|
|
14031
|
+
sha256: external_exports.string().regex(/^[a-f0-9]{64}$/i),
|
|
14032
|
+
width: external_exports.number().int().positive(),
|
|
14033
|
+
height: external_exports.number().int().positive()
|
|
14034
|
+
}).nullable()
|
|
14035
|
+
// null for non-image uploads
|
|
14036
|
+
});
|
|
14037
|
+
var variantKindSchema = external_exports.enum([
|
|
14038
|
+
"RAW",
|
|
14039
|
+
"VIEW",
|
|
14040
|
+
"PREMIUM_HD",
|
|
14041
|
+
"PREMIUM_RAW",
|
|
14042
|
+
"THUMBNAIL"
|
|
14043
|
+
]);
|
|
14044
|
+
var adminUploadsGetVariantUrlSchema = external_exports.object({
|
|
14045
|
+
uploadId: external_exports.string(),
|
|
14046
|
+
kind: variantKindSchema,
|
|
14047
|
+
expiresIn: external_exports.number().int().positive().max(3600).default(300)
|
|
14048
|
+
});
|
|
14049
|
+
var getUploadSchema = external_exports.object({
|
|
14050
|
+
id: external_exports.string()
|
|
14051
|
+
});
|
|
14052
|
+
var priceSchema = external_exports.number().int().min(0, "Price must be a positive integer in cents");
|
|
14053
|
+
var checkPurchaseSchema = external_exports.object({
|
|
14054
|
+
productId: external_exports.string(),
|
|
14055
|
+
type: external_exports.enum(["STRIPE", "GOOGLE", "APPLE"]),
|
|
14056
|
+
receivedPrice: external_exports.number().int().min(0, "Received price must be a positive integer in cents"),
|
|
14057
|
+
currency: external_exports.string().length(3).default("USD")
|
|
14058
|
+
});
|
|
14059
|
+
var creditPurchaseSchema = external_exports.object({
|
|
14060
|
+
orderId: external_exports.string(),
|
|
14061
|
+
transactionDate: external_exports.number(),
|
|
14062
|
+
receivedPrice: external_exports.number().int().min(0, "Received price must be a positive integer in cents"),
|
|
14063
|
+
type: external_exports.enum(["STRIPE", "GOOGLE", "APPLE"]),
|
|
14064
|
+
secret: external_exports.string(),
|
|
14065
|
+
// Required for APPLE/GOOGLE IAP — used for server-side receipt verification
|
|
14066
|
+
purchaseToken: external_exports.string().optional()
|
|
14067
|
+
});
|
|
14068
|
+
var dsarFormSchema = external_exports.object({
|
|
14069
|
+
name: external_exports.string().trim().min(1, "Please enter your name.").max(100, "Please keep your name under 100 characters."),
|
|
14070
|
+
email: external_exports.string().trim().min(1, "Please enter your email address.").email("Please enter a valid email address."),
|
|
14071
|
+
selectedAs: external_exports.enum(["person", "agent"]),
|
|
14072
|
+
requestUnder: external_exports.enum(["GDPR", "CCPA", "OTHER"]),
|
|
14073
|
+
selectedTo: external_exports.enum(["knowledge", "delete", "optOut", "optIn", "other"]),
|
|
14074
|
+
details: external_exports.string().trim().min(1, "Please provide details about your request.").max(5e3, "Please keep details under 5000 characters."),
|
|
14075
|
+
confirmAccuracy: external_exports.boolean().refine((value2) => value2, "Please confirm your information is accurate."),
|
|
14076
|
+
confirmConsequences: external_exports.boolean().refine(
|
|
14077
|
+
(value2) => value2,
|
|
14078
|
+
"Please acknowledge the deletion/restriction consequences."
|
|
14079
|
+
),
|
|
14080
|
+
confirmVerification: external_exports.boolean().refine((value2) => value2, "Please acknowledge the email verification step.")
|
|
14081
|
+
});
|
|
14082
|
+
var mapLinkSchema = external_exports.object({
|
|
14083
|
+
startPosition: external_exports.object({
|
|
14084
|
+
postId: external_exports.string().optional(),
|
|
14085
|
+
latitude: external_exports.number().min(-90).max(90, { message: "Latitude must be between -90 and 90" }),
|
|
14086
|
+
longitude: external_exports.number().min(-180).max(180, { message: "Longitude must be between -180 and 180" })
|
|
14087
|
+
}),
|
|
14088
|
+
pins: external_exports.array(
|
|
14089
|
+
external_exports.object({
|
|
14090
|
+
postId: external_exports.string().uuid({ message: "Pin ID must be a valid UUID" }),
|
|
14091
|
+
latitude: external_exports.number().min(-90).max(90, { message: "Pin latitude must be between -90 and 90" }),
|
|
14092
|
+
longitude: external_exports.number().min(-180).max(180, { message: "Pin longitude must be between -180 and 180" })
|
|
14093
|
+
})
|
|
14094
|
+
).optional().default([])
|
|
14095
|
+
});
|
|
14096
|
+
var mapLinkPostIds = mapLinkSchema.transform((data) => {
|
|
13733
14097
|
return {
|
|
13734
|
-
|
|
13735
|
-
|
|
13736
|
-
|
|
13737
|
-
|
|
13738
|
-
|
|
13739
|
-
|
|
14098
|
+
startPosition: {
|
|
14099
|
+
postId: data.startPosition.postId,
|
|
14100
|
+
latitude: data.startPosition.latitude,
|
|
14101
|
+
longitude: data.startPosition.longitude
|
|
14102
|
+
},
|
|
14103
|
+
pins: data.pins.map((pin) => ({
|
|
14104
|
+
postId: pin.postId,
|
|
14105
|
+
latitude: pin.latitude,
|
|
14106
|
+
longitude: pin.longitude
|
|
14107
|
+
}))
|
|
14108
|
+
};
|
|
14109
|
+
});
|
|
14110
|
+
var mapLinkHref = mapLinkSchema.transform((data) => {
|
|
14111
|
+
const latParam = `latitude=${encodeURIComponent(data.startPosition.latitude)}`;
|
|
14112
|
+
const lonParam = `longitude=${encodeURIComponent(
|
|
14113
|
+
data.startPosition.longitude
|
|
14114
|
+
)}`;
|
|
14115
|
+
const pinsParam = data.pins.length > 0 ? `pins=${encodeURIComponent(JSON.stringify(data.pins))}` : "";
|
|
14116
|
+
return {
|
|
14117
|
+
startPosition: {
|
|
14118
|
+
postId: data.startPosition.postId,
|
|
14119
|
+
latitude: data.startPosition.latitude,
|
|
14120
|
+
longitude: data.startPosition.longitude
|
|
14121
|
+
},
|
|
14122
|
+
href: `${data.startPosition.postId ? `postId=${data.startPosition.postId}&` : ""}${latParam}&${lonParam}${pinsParam ? `&${pinsParam}` : ""}`
|
|
13740
14123
|
};
|
|
14124
|
+
});
|
|
14125
|
+
var wikipediaUrlSchema = external_exports.string().refine(
|
|
14126
|
+
(url2) => {
|
|
14127
|
+
try {
|
|
14128
|
+
const parsedUrl = new URL(url2);
|
|
14129
|
+
return parsedUrl.hostname.endsWith(".wikipedia.org") && parsedUrl.protocol === "https:" && parsedUrl.pathname.startsWith("/wiki/") && parsedUrl.pathname.length > 6;
|
|
14130
|
+
} catch {
|
|
14131
|
+
return false;
|
|
14132
|
+
}
|
|
14133
|
+
},
|
|
14134
|
+
{
|
|
14135
|
+
message: "Invalid Wikipedia URL. Must be a valid HTTPS URL from wikipedia.org with /wiki/ path and article title"
|
|
14136
|
+
}
|
|
14137
|
+
);
|
|
14138
|
+
var localPreferenceSchema = external_exports.object({
|
|
14139
|
+
postFilterType: external_exports.enum(["trending", "new", "popular"]),
|
|
14140
|
+
postSortBy: external_exports.nativeEnum(postDataSort),
|
|
14141
|
+
filterBot: external_exports.boolean(),
|
|
14142
|
+
filterNew: external_exports.boolean(),
|
|
14143
|
+
showBoards: external_exports.boolean(),
|
|
14144
|
+
displayMode: external_exports.enum(["data", "posts", "sortByFactiii", "mediaSearch"]),
|
|
14145
|
+
postModeType: external_exports.enum(["home", "explore"]),
|
|
14146
|
+
muteVideo: external_exports.boolean(),
|
|
14147
|
+
theme: external_exports.enum(["dark", "light"]),
|
|
14148
|
+
selectedFactiiis: external_exports.array(external_exports.number()),
|
|
14149
|
+
dataFilter: external_exports.string(),
|
|
14150
|
+
mediaSearch: external_exports.string()
|
|
14151
|
+
});
|
|
14152
|
+
var localPreferenceUrlParamsSchema = external_exports.object({
|
|
14153
|
+
postFilterType: external_exports.enum(["trending", "new", "popular"]).optional(),
|
|
14154
|
+
postSortBy: external_exports.nativeEnum(postDataSort).optional(),
|
|
14155
|
+
filterBot: external_exports.preprocess((val) => val === "true", external_exports.boolean()).optional(),
|
|
14156
|
+
showBoards: external_exports.preprocess((val) => val === "true", external_exports.boolean()).optional(),
|
|
14157
|
+
displayMode: external_exports.enum(["data", "posts", "sortByFactiii", "mediaSearch"]).optional(),
|
|
14158
|
+
postModeType: external_exports.enum(["home", "explore"]).optional(),
|
|
14159
|
+
selectedFactiiis: external_exports.preprocess((val) => {
|
|
14160
|
+
if (!val || typeof val !== "string") return [];
|
|
14161
|
+
return val.split(",").map(Number).filter((id) => !isNaN(id));
|
|
14162
|
+
}, external_exports.array(external_exports.number())).optional()
|
|
14163
|
+
});
|
|
14164
|
+
var productSchema = external_exports.object({
|
|
14165
|
+
id: external_exports.number(),
|
|
14166
|
+
title: external_exports.string(),
|
|
14167
|
+
description: external_exports.string().nullable(),
|
|
14168
|
+
price: external_exports.number(),
|
|
14169
|
+
createdAt: external_exports.date(),
|
|
14170
|
+
active: external_exports.boolean(),
|
|
14171
|
+
originalInventory: external_exports.number().optional().nullable().transform((val) => val === void 0 ? null : val),
|
|
14172
|
+
inventory: external_exports.number(),
|
|
14173
|
+
discount: external_exports.number(),
|
|
14174
|
+
type: external_exports.enum(["TRON", "MONTHLY_SUBSCRIPTION", "FOUNDERS_TOKEN", "DONATION"]),
|
|
14175
|
+
appStoreProductId: external_exports.string().nullable(),
|
|
14176
|
+
playStoreProductId: external_exports.string().nullable(),
|
|
14177
|
+
stripePriceId: external_exports.string().nullable(),
|
|
14178
|
+
spaceSlug: external_exports.string(),
|
|
14179
|
+
images: external_exports.array(external_exports.string()),
|
|
14180
|
+
// Trons granted to the buyer on purchase + premium days granted. Only meaningful
|
|
14181
|
+
// for TRON and FOUNDERS_TOKEN; other product types ignore these. Optional so the
|
|
14182
|
+
// admin form can omit them when editing subscription/donation rows.
|
|
14183
|
+
tronQuantity: external_exports.number().int().min(0).optional(),
|
|
14184
|
+
tronPremiumDays: external_exports.number().int().min(0).optional()
|
|
14185
|
+
});
|
|
14186
|
+
var adminTransferSpaceOwnershipSchema = external_exports.object({
|
|
14187
|
+
spaceId: external_exports.number(),
|
|
14188
|
+
newOwnerUserId: external_exports.number()
|
|
14189
|
+
});
|
|
14190
|
+
var baseSchema = {
|
|
14191
|
+
cursor: external_exports.union([external_exports.string(), external_exports.number()]).optional(),
|
|
14192
|
+
type: external_exports.enum(["new", "trending", "popular", "board"]).optional(),
|
|
14193
|
+
filterBots: external_exports.boolean().optional(),
|
|
14194
|
+
// Omitted = show; hiding new accounts is opt-in via the quick filter.
|
|
14195
|
+
filterNewAccounts: external_exports.boolean().optional(),
|
|
14196
|
+
showBoards: external_exports.boolean().optional(),
|
|
14197
|
+
factiiiIds: external_exports.number().array().optional(),
|
|
14198
|
+
strictFactiiiIds: external_exports.number().array().optional(),
|
|
14199
|
+
//used for getting posts by relatedfactiiicountgroup
|
|
14200
|
+
dataFilter: external_exports.string().optional(),
|
|
14201
|
+
start: external_exports.date().optional(),
|
|
14202
|
+
end: external_exports.date().optional()
|
|
14203
|
+
};
|
|
14204
|
+
var postFeedInputSchema = external_exports.discriminatedUnion("source", [
|
|
14205
|
+
external_exports.object({
|
|
14206
|
+
source: external_exports.literal("home"),
|
|
14207
|
+
...baseSchema
|
|
14208
|
+
}),
|
|
14209
|
+
external_exports.object({
|
|
14210
|
+
source: external_exports.literal("explore"),
|
|
14211
|
+
...baseSchema
|
|
14212
|
+
}),
|
|
14213
|
+
external_exports.object({
|
|
14214
|
+
source: external_exports.literal("user"),
|
|
14215
|
+
username: external_exports.string(),
|
|
14216
|
+
...baseSchema
|
|
14217
|
+
}),
|
|
14218
|
+
external_exports.object({
|
|
14219
|
+
source: external_exports.literal("space"),
|
|
14220
|
+
spaceSlug: external_exports.string(),
|
|
14221
|
+
spaceRenderType: external_exports.enum(["USER_FACING", "SPACE_FACING", "BOTH"]).optional(),
|
|
14222
|
+
...baseSchema
|
|
14223
|
+
}),
|
|
14224
|
+
external_exports.object({
|
|
14225
|
+
source: external_exports.literal("saved"),
|
|
14226
|
+
...baseSchema
|
|
14227
|
+
}),
|
|
14228
|
+
external_exports.object({
|
|
14229
|
+
source: external_exports.literal("factiiis"),
|
|
14230
|
+
...baseSchema
|
|
14231
|
+
}),
|
|
14232
|
+
// Every anonymous post, author masked. Backs /user/Anonymous%20User, which is
|
|
14233
|
+
// where anonymous posts' author links point.
|
|
14234
|
+
external_exports.object({
|
|
14235
|
+
source: external_exports.literal("anonymous"),
|
|
14236
|
+
...baseSchema
|
|
14237
|
+
}),
|
|
14238
|
+
external_exports.object({
|
|
14239
|
+
source: external_exports.literal("search"),
|
|
14240
|
+
query: external_exports.string().optional(),
|
|
14241
|
+
spaceRenderType: external_exports.enum(["USER_FACING", "SPACE_FACING", "BOTH"]).optional(),
|
|
14242
|
+
userIds: external_exports.number().array().optional(),
|
|
14243
|
+
spaceIds: external_exports.number().array().optional(),
|
|
14244
|
+
...baseSchema
|
|
14245
|
+
})
|
|
14246
|
+
]);
|
|
14247
|
+
var donateTronsSchema = external_exports.object({
|
|
14248
|
+
amount: external_exports.number().int().positive()
|
|
14249
|
+
});
|
|
14250
|
+
var communityPoolModeSchema = external_exports.enum(["OFF", "WHITELIST", "OPEN"]);
|
|
14251
|
+
var communityPoolConfigSchema = external_exports.object({
|
|
14252
|
+
mode: communityPoolModeSchema,
|
|
14253
|
+
userCapPer4h: external_exports.number().int().min(0),
|
|
14254
|
+
poolCapPer4h: external_exports.number().int().min(0)
|
|
14255
|
+
});
|
|
14256
|
+
var crossPostPlatformEnum = external_exports.enum([
|
|
14257
|
+
"TIKTOK",
|
|
14258
|
+
"YOUTUBE",
|
|
14259
|
+
"INSTAGRAM",
|
|
14260
|
+
"FACEBOOK",
|
|
14261
|
+
"TWITTER",
|
|
14262
|
+
"LINKEDIN",
|
|
14263
|
+
"REDDIT"
|
|
14264
|
+
]);
|
|
14265
|
+
var crossPostSchema = external_exports.object({
|
|
14266
|
+
postId: external_exports.number().int().positive(),
|
|
14267
|
+
platforms: external_exports.array(crossPostPlatformEnum).min(1)
|
|
14268
|
+
});
|
|
14269
|
+
var tiktokPostOptionsSchema = external_exports.object({
|
|
14270
|
+
postId: external_exports.number().int().positive(),
|
|
14271
|
+
privacyLevel: external_exports.enum([
|
|
14272
|
+
"PUBLIC_TO_EVERYONE",
|
|
14273
|
+
"MUTUAL_FOLLOW_FRIENDS",
|
|
14274
|
+
"FOLLOWER_OF_CREATOR",
|
|
14275
|
+
"SELF_ONLY"
|
|
14276
|
+
]),
|
|
14277
|
+
disableComment: external_exports.boolean().default(false),
|
|
14278
|
+
disableDuet: external_exports.boolean().default(false),
|
|
14279
|
+
disableStitch: external_exports.boolean().default(false),
|
|
14280
|
+
videoCoverTimestampMs: external_exports.number().int().min(0).optional(),
|
|
14281
|
+
isAiGenerated: external_exports.boolean().default(false)
|
|
14282
|
+
});
|
|
14283
|
+
var crossPostSubmitSchema = external_exports.object({
|
|
14284
|
+
postId: external_exports.string(),
|
|
14285
|
+
platforms: external_exports.array(crossPostPlatformEnum).min(1).max(5)
|
|
14286
|
+
});
|
|
14287
|
+
var apiCostUpdateSchema = external_exports.object({
|
|
14288
|
+
platform: crossPostPlatformEnum,
|
|
14289
|
+
readCostTrons: external_exports.number().int().min(0),
|
|
14290
|
+
pushCostTrons: external_exports.number().int().min(0),
|
|
14291
|
+
costSource: external_exports.enum(["manual", "api"]),
|
|
14292
|
+
notes: external_exports.string().max(500).optional()
|
|
14293
|
+
});
|
|
14294
|
+
var startSessionSchema = external_exports.object({
|
|
14295
|
+
isDaily: external_exports.boolean().default(false)
|
|
14296
|
+
});
|
|
14297
|
+
var submitAnswerSchema = external_exports.object({
|
|
14298
|
+
sessionId: external_exports.number().int(),
|
|
14299
|
+
postId: external_exports.number().int(),
|
|
14300
|
+
answeredFact: external_exports.boolean(),
|
|
14301
|
+
timeMs: external_exports.number().int().min(0).max(15e3)
|
|
14302
|
+
});
|
|
14303
|
+
var endSessionSchema = external_exports.object({
|
|
14304
|
+
sessionId: external_exports.number().int()
|
|
14305
|
+
});
|
|
14306
|
+
var getLeaderboardSchema = external_exports.object({
|
|
14307
|
+
period: external_exports.enum(["daily", "weekly", "allTime"]),
|
|
14308
|
+
limit: external_exports.number().int().min(1).max(50).default(50)
|
|
14309
|
+
});
|
|
14310
|
+
var getSessionResultSchema = external_exports.object({
|
|
14311
|
+
sessionId: external_exports.number().int()
|
|
14312
|
+
});
|
|
14313
|
+
var forgeElementName = external_exports.string().trim().min(1).max(24);
|
|
14314
|
+
var forgeCombineSchema = external_exports.object({
|
|
14315
|
+
a: forgeElementName,
|
|
14316
|
+
b: forgeElementName
|
|
14317
|
+
});
|
|
14318
|
+
var forgeLeaderboardSchema = external_exports.object({
|
|
14319
|
+
limit: external_exports.number().int().min(1).max(50).default(25)
|
|
14320
|
+
});
|
|
14321
|
+
var arcadeGameSlug = external_exports.enum([
|
|
14322
|
+
"circle",
|
|
14323
|
+
"password",
|
|
14324
|
+
"forge",
|
|
14325
|
+
"spend",
|
|
14326
|
+
"dilemmas",
|
|
14327
|
+
"clicker",
|
|
14328
|
+
"deep",
|
|
14329
|
+
"robot",
|
|
14330
|
+
"space"
|
|
14331
|
+
]);
|
|
14332
|
+
var recordGameStatSchema = external_exports.object({
|
|
14333
|
+
game: arcadeGameSlug,
|
|
14334
|
+
/** Canonical sortable score for that game; direction is decided client-side. */
|
|
14335
|
+
best: external_exports.number().finite(),
|
|
14336
|
+
/** Per-game extras, kept small — this lands in a JSON column. */
|
|
14337
|
+
detail: external_exports.record(external_exports.string(), external_exports.union([external_exports.string(), external_exports.number()])).optional()
|
|
14338
|
+
});
|
|
14339
|
+
var dilemmaVoteSchema = external_exports.object({
|
|
14340
|
+
dilemmaId: external_exports.number().int().min(1).max(200),
|
|
14341
|
+
pulled: external_exports.boolean()
|
|
14342
|
+
});
|
|
14343
|
+
var SKILL_NAME_REGEX = /^[a-zA-Z0-9._-]+$/;
|
|
14344
|
+
var skillNameSchema = external_exports.string().min(1).max(64).regex(SKILL_NAME_REGEX, {
|
|
14345
|
+
message: "Use only letters, numbers, dots, dashes and underscores"
|
|
14346
|
+
});
|
|
14347
|
+
var skillFrontmatterSchema = external_exports.object({
|
|
14348
|
+
name: skillNameSchema,
|
|
14349
|
+
description: external_exports.string().min(1, { message: "Description is required" }),
|
|
14350
|
+
// Both CLIs accept a comma-separated string or a YAML list; normalised to
|
|
14351
|
+
// a list on parse and written back as a list.
|
|
14352
|
+
"allowed-tools": external_exports.array(external_exports.string()).optional(),
|
|
14353
|
+
model: external_exports.string().optional(),
|
|
14354
|
+
license: external_exports.string().optional(),
|
|
14355
|
+
metadata: external_exports.record(external_exports.string(), external_exports.string()).optional(),
|
|
14356
|
+
"disable-model-invocation": external_exports.literal(false).optional()
|
|
14357
|
+
}).strict();
|
|
14358
|
+
var skillAgentSchema = external_exports.enum(["claude", "codex"]);
|
|
14359
|
+
var skillScopeSchema = external_exports.enum(["global", "repo"]);
|
|
14360
|
+
|
|
14361
|
+
// ../../shared/all/helpers/board-agent-core/host-deploy.ts
|
|
14362
|
+
var import_child_process3 = require("child_process");
|
|
14363
|
+
var import_promises2 = __toESM(require("fs/promises"));
|
|
14364
|
+
var import_path4 = __toESM(require("path"));
|
|
14365
|
+
var import_util5 = require("util");
|
|
14366
|
+
var execFileAsync2 = (0, import_util5.promisify)(import_child_process3.execFile);
|
|
14367
|
+
var SKILL_DIR = ".agents/skills/deploy";
|
|
14368
|
+
var SKILL_PATH = `${SKILL_DIR}/SKILL.md`;
|
|
14369
|
+
var VARIABLES_PATH = `${SKILL_DIR}/required-variables.json`;
|
|
14370
|
+
var CHECKER_PATH = `${SKILL_DIR}/check-environment.sh`;
|
|
14371
|
+
var CLAUDE_SKILLS_DIR = ".claude/skills";
|
|
14372
|
+
var CLAUDE_LINK = `${CLAUDE_SKILLS_DIR}/deploy`;
|
|
14373
|
+
async function ensureClaudeSkillLink(workspace) {
|
|
14374
|
+
const linkPath = import_path4.default.join(workspace, CLAUDE_LINK);
|
|
14375
|
+
try {
|
|
14376
|
+
await import_promises2.default.lstat(linkPath);
|
|
14377
|
+
return;
|
|
14378
|
+
} catch {
|
|
14379
|
+
}
|
|
14380
|
+
await import_promises2.default.mkdir(import_path4.default.join(workspace, CLAUDE_SKILLS_DIR), { recursive: true });
|
|
14381
|
+
await import_promises2.default.symlink("../../.agents/skills/deploy", linkPath, "dir");
|
|
13741
14382
|
}
|
|
13742
|
-
|
|
13743
|
-
|
|
13744
|
-
|
|
14383
|
+
var GIT_NO_HELPER = ["-c", "credential.helper="];
|
|
14384
|
+
var GIT_ENV = { ...process.env, GIT_TERMINAL_PROMPT: "0" };
|
|
14385
|
+
async function hostGit(cwd, ...args) {
|
|
14386
|
+
const { stdout } = await execFileAsync2("git", [...GIT_NO_HELPER, ...args], {
|
|
14387
|
+
cwd,
|
|
14388
|
+
env: GIT_ENV,
|
|
14389
|
+
maxBuffer: 16 * 1024 * 1024
|
|
14390
|
+
});
|
|
14391
|
+
return stdout;
|
|
13745
14392
|
}
|
|
13746
|
-
async function
|
|
13747
|
-
const
|
|
13748
|
-
const
|
|
13749
|
-
|
|
13750
|
-
|
|
13751
|
-
|
|
13752
|
-
|
|
14393
|
+
async function gitCatFileBatch(cwd, shas) {
|
|
14394
|
+
const out = /* @__PURE__ */ new Map();
|
|
14395
|
+
const wanted = [...new Set(shas.filter(Boolean))];
|
|
14396
|
+
if (!wanted.length) return out;
|
|
14397
|
+
const child = (0, import_child_process3.spawn)("git", [...GIT_NO_HELPER, "cat-file", "--batch"], {
|
|
14398
|
+
cwd,
|
|
14399
|
+
env: GIT_ENV
|
|
14400
|
+
});
|
|
14401
|
+
const chunks = [];
|
|
14402
|
+
child.stdout.on("data", (c) => chunks.push(c));
|
|
14403
|
+
const done = new Promise((resolve2) => {
|
|
14404
|
+
child.on("close", () => resolve2());
|
|
14405
|
+
child.on("error", () => resolve2());
|
|
14406
|
+
});
|
|
14407
|
+
child.stdin.end(`${wanted.join("\n")}
|
|
14408
|
+
`);
|
|
14409
|
+
await done;
|
|
14410
|
+
let buf = Buffer.concat(chunks);
|
|
14411
|
+
while (buf.length) {
|
|
14412
|
+
const nl = buf.indexOf(10);
|
|
14413
|
+
if (nl === -1) break;
|
|
14414
|
+
const header = buf.subarray(0, nl).toString("utf-8");
|
|
14415
|
+
buf = buf.subarray(nl + 1);
|
|
14416
|
+
const [sha, type, sizeText] = header.split(" ");
|
|
14417
|
+
if (type === void 0 || sizeText === void 0) continue;
|
|
14418
|
+
const size = Number(sizeText);
|
|
14419
|
+
if (!Number.isFinite(size)) break;
|
|
14420
|
+
out.set(sha, buf.subarray(0, size).toString("utf-8"));
|
|
14421
|
+
buf = buf.subarray(size + 1);
|
|
14422
|
+
}
|
|
14423
|
+
return out;
|
|
14424
|
+
}
|
|
14425
|
+
async function ensureWorkspaceClone(opts) {
|
|
14426
|
+
const workspace = import_path4.default.join(opts.spaceDir, "workspace");
|
|
14427
|
+
const branch = (opts.mainBranch || "main").replace(/[^\w./-]/g, "");
|
|
14428
|
+
const url2 = buildCloneUrl(opts.repoUrl, opts.githubToken);
|
|
14429
|
+
let cloned = false;
|
|
14430
|
+
try {
|
|
14431
|
+
await hostGit(workspace, "rev-parse", "--git-dir");
|
|
14432
|
+
await hostGit(workspace, "remote", "set-url", "origin", url2);
|
|
14433
|
+
} catch {
|
|
14434
|
+
const leftovers = await import_promises2.default.readdir(workspace).catch(() => []);
|
|
14435
|
+
if (leftovers.includes(".git")) {
|
|
14436
|
+
throw new Error(
|
|
14437
|
+
`${workspace} holds a git repository that cannot be read. Remove it and retry.`
|
|
14438
|
+
);
|
|
14439
|
+
}
|
|
14440
|
+
for (const name of leftovers) {
|
|
14441
|
+
await import_promises2.default.rm(import_path4.default.join(workspace, name), { recursive: true, force: true });
|
|
14442
|
+
}
|
|
14443
|
+
await execFileAsync2(
|
|
14444
|
+
"git",
|
|
14445
|
+
[...GIT_NO_HELPER, "clone", "--branch", branch, url2, workspace],
|
|
14446
|
+
{ env: GIT_ENV }
|
|
13753
14447
|
);
|
|
14448
|
+
cloned = true;
|
|
13754
14449
|
}
|
|
13755
|
-
|
|
13756
|
-
|
|
13757
|
-
if (staged?.size === source.size) return;
|
|
13758
|
-
await import_promises2.default.mkdir(p.binDir, { recursive: true });
|
|
13759
|
-
await import_promises2.default.copyFile(src, p.binary);
|
|
13760
|
-
await import_promises2.default.chmod(p.binary, 493);
|
|
14450
|
+
if (!cloned) await hostGit(workspace, "fetch", "origin", branch);
|
|
14451
|
+
return { workspace, branch };
|
|
13761
14452
|
}
|
|
13762
|
-
async function
|
|
13763
|
-
const {
|
|
13764
|
-
const
|
|
13765
|
-
|
|
13766
|
-
|
|
13767
|
-
|
|
13768
|
-
|
|
13769
|
-
|
|
13770
|
-
|
|
13771
|
-
|
|
14453
|
+
async function checkDeployStatus(opts) {
|
|
14454
|
+
const { workspace, branch } = await ensureWorkspaceClone(opts);
|
|
14455
|
+
const has = async (refPath) => {
|
|
14456
|
+
try {
|
|
14457
|
+
await hostGit(workspace, "cat-file", "-e", refPath);
|
|
14458
|
+
return true;
|
|
14459
|
+
} catch {
|
|
14460
|
+
return false;
|
|
14461
|
+
}
|
|
14462
|
+
};
|
|
14463
|
+
const hasAll = async (ref) => await has(`${ref}:${SKILL_PATH}`) && await has(`${ref}:${VARIABLES_PATH}`) && await has(`${ref}:${CHECKER_PATH}`);
|
|
14464
|
+
let pendingBranch = "";
|
|
14465
|
+
if (!await hasAll(`origin/${branch}`)) {
|
|
14466
|
+
try {
|
|
14467
|
+
await hostGit(
|
|
14468
|
+
workspace,
|
|
14469
|
+
"fetch",
|
|
14470
|
+
"origin",
|
|
14471
|
+
"+refs/heads/deploy-skill*:refs/remotes/origin/deploy-skill*"
|
|
14472
|
+
);
|
|
14473
|
+
const refs = await hostGit(
|
|
14474
|
+
workspace,
|
|
14475
|
+
"for-each-ref",
|
|
14476
|
+
"--sort=-committerdate",
|
|
14477
|
+
"--format=%(refname:short)",
|
|
14478
|
+
"refs/remotes/origin/deploy-skill*"
|
|
14479
|
+
);
|
|
14480
|
+
for (const ref of refs.split("\n").filter(Boolean)) {
|
|
14481
|
+
if (await hasAll(ref)) {
|
|
14482
|
+
pendingBranch = ref.replace(/^origin\//, "");
|
|
14483
|
+
break;
|
|
14484
|
+
}
|
|
14485
|
+
}
|
|
14486
|
+
} catch {
|
|
14487
|
+
}
|
|
14488
|
+
}
|
|
14489
|
+
const hasVariables = await has(`origin/${branch}:${VARIABLES_PATH}`);
|
|
14490
|
+
let variables = [];
|
|
14491
|
+
if (hasVariables) {
|
|
14492
|
+
try {
|
|
14493
|
+
const raw = await hostGit(
|
|
14494
|
+
workspace,
|
|
14495
|
+
"cat-file",
|
|
14496
|
+
"-p",
|
|
14497
|
+
`origin/${branch}:${VARIABLES_PATH}`
|
|
14498
|
+
);
|
|
14499
|
+
variables = external_exports.array(requiredVariableSchema).parse(JSON.parse(raw));
|
|
14500
|
+
} catch {
|
|
14501
|
+
}
|
|
14502
|
+
}
|
|
14503
|
+
return {
|
|
14504
|
+
hasSkill: await has(`origin/${branch}:${SKILL_PATH}`),
|
|
14505
|
+
hasVariables,
|
|
14506
|
+
hasChecker: await has(`origin/${branch}:${CHECKER_PATH}`),
|
|
14507
|
+
branch,
|
|
14508
|
+
pendingBranch,
|
|
14509
|
+
variables
|
|
14510
|
+
};
|
|
14511
|
+
}
|
|
14512
|
+
var requiredVariableSchema = external_exports.object({
|
|
14513
|
+
key: external_exports.string().min(1),
|
|
14514
|
+
title: external_exports.string().catch(""),
|
|
14515
|
+
description: external_exports.string().catch(""),
|
|
14516
|
+
category: external_exports.string().catch("general"),
|
|
14517
|
+
sensitive: external_exports.boolean().catch(true)
|
|
14518
|
+
});
|
|
14519
|
+
function spaceEnv(spaceDir) {
|
|
14520
|
+
const paths = hostSpacePaths(spaceDir);
|
|
14521
|
+
return {
|
|
14522
|
+
WORKSPACE: paths.workspace,
|
|
14523
|
+
BACKUPS_DIR: paths.backups,
|
|
14524
|
+
BUILDS_DIR: paths.builds,
|
|
14525
|
+
// No keychain credential helper and no interactive git prompts anywhere
|
|
14526
|
+
// in the deploy surface (agent sessions + authorized commands): auth
|
|
14527
|
+
// comes from the secrets store or the tokened URL, never the keychain.
|
|
14528
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
14529
|
+
GIT_CONFIG_COUNT: "1",
|
|
14530
|
+
GIT_CONFIG_KEY_0: "credential.helper",
|
|
14531
|
+
GIT_CONFIG_VALUE_0: ""
|
|
14532
|
+
};
|
|
14533
|
+
}
|
|
14534
|
+
async function readContract(workspace) {
|
|
14535
|
+
const skill = await import_promises2.default.readFile(import_path4.default.join(workspace, SKILL_PATH), "utf-8").catch(() => {
|
|
14536
|
+
throw new Error("The creator wrote no SKILL.md.");
|
|
14537
|
+
});
|
|
14538
|
+
const checker = await import_promises2.default.readFile(import_path4.default.join(workspace, CHECKER_PATH), "utf-8").catch(() => {
|
|
14539
|
+
throw new Error("The creator wrote no check-environment.sh.");
|
|
14540
|
+
});
|
|
14541
|
+
try {
|
|
14542
|
+
const raw = JSON.parse(
|
|
14543
|
+
await import_promises2.default.readFile(import_path4.default.join(workspace, VARIABLES_PATH), "utf-8")
|
|
14544
|
+
);
|
|
14545
|
+
return {
|
|
14546
|
+
skill,
|
|
14547
|
+
checker,
|
|
14548
|
+
variables: external_exports.array(requiredVariableSchema).parse(raw)
|
|
14549
|
+
};
|
|
14550
|
+
} catch {
|
|
14551
|
+
throw new Error("The creator wrote no valid required-variables.json.");
|
|
14552
|
+
}
|
|
14553
|
+
}
|
|
14554
|
+
async function generateDeploySkill(opts) {
|
|
14555
|
+
const { instructions, sendLog } = opts;
|
|
14556
|
+
if (!instructions.trim()) {
|
|
14557
|
+
throw new Error("Describe your deployment process first.");
|
|
14558
|
+
}
|
|
14559
|
+
const status = await checkDeployStatus(opts);
|
|
14560
|
+
const workspace = import_path4.default.join(opts.spaceDir, "workspace");
|
|
14561
|
+
sendLog({
|
|
14562
|
+
type: "system",
|
|
14563
|
+
content: `Resetting workspace to origin/${status.branch}...`
|
|
14564
|
+
});
|
|
14565
|
+
await hostGit(
|
|
14566
|
+
workspace,
|
|
14567
|
+
"checkout",
|
|
14568
|
+
"-B",
|
|
14569
|
+
status.branch,
|
|
14570
|
+
`origin/${status.branch}`
|
|
14571
|
+
);
|
|
14572
|
+
await hostGit(workspace, "clean", "-fd");
|
|
14573
|
+
sendLog({
|
|
14574
|
+
type: "system",
|
|
14575
|
+
content: "Analyzing the repo and writing the deploy skill..."
|
|
14576
|
+
});
|
|
14577
|
+
const { result } = await spawnAgent(
|
|
14578
|
+
opts.provider,
|
|
14579
|
+
hostTarget(opts.spaceDir),
|
|
14580
|
+
{
|
|
14581
|
+
cwd: workspace,
|
|
14582
|
+
prompt: buildPrompt(prompts_default.deployCreate, {
|
|
14583
|
+
INSTRUCTIONS: instructions
|
|
14584
|
+
}),
|
|
14585
|
+
allowedTools: "Read,Grep,Glob,Bash,Edit,Write,WebSearch,WebFetch",
|
|
14586
|
+
// auto: the per-action classifier blocks escalations/unrecognized-infra
|
|
14587
|
+
// actions - a host session with no human reviewing each command should
|
|
14588
|
+
// not run bypassPermissions. (Codex maps this to a workspace-write
|
|
14589
|
+
// sandbox; see agent.ts.)
|
|
14590
|
+
permissionMode: "auto",
|
|
14591
|
+
model: "claude-opus-4-6",
|
|
14592
|
+
includePartialMessages: true,
|
|
14593
|
+
env: spaceEnv(opts.spaceDir),
|
|
14594
|
+
callbacks: { onLog: (entry) => sendLog(entry) }
|
|
14595
|
+
}
|
|
14596
|
+
);
|
|
14597
|
+
const { sessionId } = await result;
|
|
14598
|
+
const draft = await readContract(workspace);
|
|
14599
|
+
return { ...draft, sessionId };
|
|
14600
|
+
}
|
|
14601
|
+
async function reviseDeploySkill(opts) {
|
|
14602
|
+
const { feedback, sendLog } = opts;
|
|
14603
|
+
if (!feedback.trim()) throw new Error("Corrections are empty.");
|
|
14604
|
+
const workspace = import_path4.default.join(opts.spaceDir, "workspace");
|
|
14605
|
+
sendLog({ type: "system", content: "Applying your corrections..." });
|
|
14606
|
+
const { result } = await spawnAgent(
|
|
14607
|
+
opts.provider,
|
|
14608
|
+
hostTarget(opts.spaceDir),
|
|
14609
|
+
{
|
|
14610
|
+
cwd: workspace,
|
|
14611
|
+
prompt: buildPrompt(prompts_default.deployCreateRevise, {
|
|
14612
|
+
FEEDBACK: feedback
|
|
14613
|
+
}),
|
|
14614
|
+
allowedTools: "Read,Grep,Glob,Bash,Edit,Write,WebSearch,WebFetch",
|
|
14615
|
+
permissionMode: "auto",
|
|
14616
|
+
model: "claude-opus-4-6",
|
|
14617
|
+
includePartialMessages: true,
|
|
14618
|
+
resumeSessionId: opts.resumeSessionId || void 0,
|
|
14619
|
+
env: spaceEnv(opts.spaceDir),
|
|
14620
|
+
callbacks: { onLog: (entry) => sendLog(entry) }
|
|
14621
|
+
}
|
|
14622
|
+
);
|
|
14623
|
+
const { sessionId } = await result;
|
|
14624
|
+
const draft = await readContract(workspace);
|
|
14625
|
+
return { ...draft, sessionId: sessionId || opts.resumeSessionId || "" };
|
|
14626
|
+
}
|
|
14627
|
+
async function loadSkillDraft(opts) {
|
|
14628
|
+
const branch = opts.branch.replace(/[^\w./-]/g, "");
|
|
14629
|
+
if (!branch.startsWith("deploy-skill")) {
|
|
14630
|
+
throw new Error("Not a deploy-skill branch.");
|
|
14631
|
+
}
|
|
14632
|
+
await checkDeployStatus(opts);
|
|
14633
|
+
const workspace = import_path4.default.join(opts.spaceDir, "workspace");
|
|
14634
|
+
await hostGit(
|
|
14635
|
+
workspace,
|
|
14636
|
+
"fetch",
|
|
14637
|
+
"origin",
|
|
14638
|
+
`+refs/heads/${branch}:refs/remotes/origin/${branch}`
|
|
14639
|
+
);
|
|
14640
|
+
await hostGit(workspace, "checkout", "-B", branch, `origin/${branch}`);
|
|
14641
|
+
await hostGit(workspace, "clean", "-fd");
|
|
14642
|
+
return readContract(workspace);
|
|
14643
|
+
}
|
|
14644
|
+
async function approveDeploySkill(opts) {
|
|
14645
|
+
const workspace = import_path4.default.join(opts.spaceDir, "workspace");
|
|
14646
|
+
await readContract(workspace);
|
|
14647
|
+
await ensureClaudeSkillLink(workspace);
|
|
14648
|
+
if (opts.gitName)
|
|
14649
|
+
await hostGit(workspace, "config", "user.name", opts.gitName);
|
|
14650
|
+
if (opts.gitEmail)
|
|
14651
|
+
await hostGit(workspace, "config", "user.email", opts.gitEmail);
|
|
14652
|
+
if (opts.onBranch) {
|
|
14653
|
+
const dirty = await hostGit(workspace, "status", "--porcelain");
|
|
14654
|
+
if (dirty.trim()) {
|
|
14655
|
+
await hostGit(workspace, "add", "-A");
|
|
14656
|
+
await hostGit(workspace, "commit", "-m", "Revise deploy skill");
|
|
14657
|
+
await hostGit(workspace, "push", "origin", opts.onBranch);
|
|
14658
|
+
}
|
|
14659
|
+
return { branch: opts.onBranch };
|
|
14660
|
+
}
|
|
14661
|
+
let target = "deploy-skill";
|
|
14662
|
+
try {
|
|
14663
|
+
await hostGit(
|
|
14664
|
+
workspace,
|
|
14665
|
+
"ls-remote",
|
|
14666
|
+
"--exit-code",
|
|
14667
|
+
"--heads",
|
|
14668
|
+
"origin",
|
|
14669
|
+
target
|
|
14670
|
+
);
|
|
14671
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace(/[-:]/g, "").replace("T", "-");
|
|
14672
|
+
target = `${target}-${stamp}`;
|
|
14673
|
+
} catch {
|
|
14674
|
+
}
|
|
14675
|
+
await hostGit(workspace, "checkout", "-B", target);
|
|
14676
|
+
await hostGit(workspace, "add", "-A");
|
|
14677
|
+
await hostGit(workspace, "commit", "-m", "Add deploy skill");
|
|
14678
|
+
await hostGit(workspace, "push", "origin", target);
|
|
14679
|
+
return { branch: target };
|
|
14680
|
+
}
|
|
14681
|
+
async function runEnvironmentCheck(opts) {
|
|
14682
|
+
const status = await checkDeployStatus(opts);
|
|
14683
|
+
if (!status.hasChecker) {
|
|
14684
|
+
throw new Error(
|
|
14685
|
+
"No environment checker on the default branch. Generate the deploy skill first."
|
|
14686
|
+
);
|
|
14687
|
+
}
|
|
14688
|
+
const workspace = import_path4.default.join(opts.spaceDir, "workspace");
|
|
14689
|
+
const script = await hostGit(
|
|
14690
|
+
workspace,
|
|
14691
|
+
"cat-file",
|
|
14692
|
+
"-p",
|
|
14693
|
+
`origin/${status.branch}:${CHECKER_PATH}`
|
|
14694
|
+
);
|
|
14695
|
+
const tmp = import_path4.default.join(opts.spaceDir, `.env-check.${process.pid}.sh`);
|
|
14696
|
+
await import_promises2.default.writeFile(tmp, script, { mode: 448 });
|
|
14697
|
+
let ok = true;
|
|
14698
|
+
let output = "";
|
|
14699
|
+
try {
|
|
14700
|
+
const scriptArg = tmp.split(import_path4.default.sep).join("/");
|
|
14701
|
+
const res = await execLogin(`bash ${JSON.stringify(scriptArg)}`, {
|
|
14702
|
+
cwd: workspace,
|
|
14703
|
+
env: { ...process.env, ...spaceEnv(opts.spaceDir) },
|
|
14704
|
+
timeout: 12e4
|
|
14705
|
+
});
|
|
14706
|
+
output = res.output;
|
|
14707
|
+
if (res.code !== 0) ok = false;
|
|
14708
|
+
} finally {
|
|
14709
|
+
await import_promises2.default.rm(tmp, { force: true });
|
|
14710
|
+
}
|
|
14711
|
+
const checks = [];
|
|
14712
|
+
for (const line of output.split("\n")) {
|
|
14713
|
+
const match = /^(OK|FAIL):\s*(.+)$/.exec(line.trim());
|
|
14714
|
+
if (!match) continue;
|
|
14715
|
+
const [, verdict, rest] = match;
|
|
14716
|
+
const dash = rest.indexOf(" - ");
|
|
14717
|
+
checks.push({
|
|
14718
|
+
ok: verdict === "OK",
|
|
14719
|
+
label: dash > 0 ? rest.slice(0, dash) : rest,
|
|
14720
|
+
detail: dash > 0 ? rest.slice(dash + 3) : ""
|
|
14721
|
+
});
|
|
14722
|
+
if (verdict === "FAIL") ok = false;
|
|
14723
|
+
}
|
|
14724
|
+
return { ok, checks, output: output.trim().slice(-4e3) };
|
|
14725
|
+
}
|
|
14726
|
+
async function fixEnvironment(opts) {
|
|
14727
|
+
const { sendLog } = opts;
|
|
14728
|
+
const before = await runEnvironmentCheck(opts);
|
|
14729
|
+
if (before.ok) return before;
|
|
14730
|
+
const status = await checkDeployStatus(opts);
|
|
14731
|
+
const workspace = import_path4.default.join(opts.spaceDir, "workspace");
|
|
14732
|
+
const script = await hostGit(
|
|
14733
|
+
workspace,
|
|
14734
|
+
"cat-file",
|
|
14735
|
+
"-p",
|
|
14736
|
+
`origin/${status.branch}:${CHECKER_PATH}`
|
|
14737
|
+
);
|
|
14738
|
+
const failed = before.checks.filter((c) => !c.ok).map((c) => `- ${c.label}${c.detail ? ` (${c.detail})` : ""}`).join("\n");
|
|
14739
|
+
sendLog({
|
|
14740
|
+
type: "system",
|
|
14741
|
+
content: "Setting this machine up to pass the environment check..."
|
|
14742
|
+
});
|
|
14743
|
+
const { result } = await spawnAgent(
|
|
14744
|
+
opts.provider,
|
|
14745
|
+
hostTarget(opts.spaceDir),
|
|
14746
|
+
{
|
|
14747
|
+
cwd: workspace,
|
|
14748
|
+
prompt: buildPrompt(prompts_default.deployEnvFix, {
|
|
14749
|
+
CHECKS: failed || before.output.slice(-2e3),
|
|
14750
|
+
SCRIPT: script
|
|
14751
|
+
}),
|
|
14752
|
+
allowedTools: "Bash,Read,Grep,Glob,WebSearch,WebFetch",
|
|
14753
|
+
// auto: installs are in-scope of the owner's explicit "fix environment"
|
|
14754
|
+
// request; the classifier still blocks anything beyond it.
|
|
14755
|
+
permissionMode: "auto",
|
|
14756
|
+
model: "claude-opus-4-6",
|
|
14757
|
+
includePartialMessages: true,
|
|
14758
|
+
env: spaceEnv(opts.spaceDir),
|
|
14759
|
+
callbacks: { onLog: (entry) => sendLog(entry) }
|
|
14760
|
+
}
|
|
14761
|
+
);
|
|
14762
|
+
await result;
|
|
14763
|
+
sendLog({ type: "system", content: "Re-running the environment check..." });
|
|
14764
|
+
return runEnvironmentCheck(opts);
|
|
14765
|
+
}
|
|
14766
|
+
async function discardDeploySkill(opts) {
|
|
14767
|
+
const workspace = import_path4.default.join(opts.spaceDir, "workspace");
|
|
14768
|
+
await hostGit(workspace, "checkout", "--", ".").catch(() => null);
|
|
14769
|
+
await hostGit(workspace, "clean", "-fd");
|
|
14770
|
+
return { success: true };
|
|
14771
|
+
}
|
|
14772
|
+
|
|
14773
|
+
// ../../shared/all/helpers/board-agent-core/skill-format.ts
|
|
14774
|
+
var EMPTY_FRONTMATTER = { name: "", description: "" };
|
|
14775
|
+
function splitKey(line) {
|
|
14776
|
+
const at = line.indexOf(":");
|
|
14777
|
+
if (at < 1) return null;
|
|
14778
|
+
return { key: line.slice(0, at).trim(), value: line.slice(at + 1).trim() };
|
|
14779
|
+
}
|
|
14780
|
+
function stripQuotes(value2) {
|
|
14781
|
+
const quoted = value2.startsWith('"') && value2.endsWith('"') || value2.startsWith("'") && value2.endsWith("'");
|
|
14782
|
+
return quoted && value2.length > 1 ? value2.slice(1, -1) : value2;
|
|
14783
|
+
}
|
|
14784
|
+
function parseSkill(raw) {
|
|
14785
|
+
const text = raw.replace(/^/, "");
|
|
14786
|
+
if (!text.startsWith("---")) {
|
|
14787
|
+
return {
|
|
14788
|
+
frontmatter: EMPTY_FRONTMATTER,
|
|
14789
|
+
body: text,
|
|
14790
|
+
errors: ["Must start with YAML frontmatter (---)."]
|
|
14791
|
+
};
|
|
14792
|
+
}
|
|
14793
|
+
const lines = text.split("\n");
|
|
14794
|
+
const end = lines.findIndex((line, i) => i > 0 && line.trim() === "---");
|
|
14795
|
+
if (end === -1) {
|
|
14796
|
+
return {
|
|
14797
|
+
frontmatter: EMPTY_FRONTMATTER,
|
|
14798
|
+
body: text,
|
|
14799
|
+
errors: ["Frontmatter is never closed (missing the second ---)."]
|
|
14800
|
+
};
|
|
14801
|
+
}
|
|
14802
|
+
const errors = [];
|
|
14803
|
+
const fields = {};
|
|
14804
|
+
let pendingKey = "";
|
|
14805
|
+
let list = [];
|
|
14806
|
+
let map = {};
|
|
14807
|
+
const flush = () => {
|
|
14808
|
+
if (pendingKey) {
|
|
14809
|
+
if (list.length) fields[pendingKey] = list;
|
|
14810
|
+
else if (Object.keys(map).length) fields[pendingKey] = map;
|
|
14811
|
+
}
|
|
14812
|
+
pendingKey = "";
|
|
14813
|
+
list = [];
|
|
14814
|
+
map = {};
|
|
14815
|
+
};
|
|
14816
|
+
for (const line of lines.slice(1, end)) {
|
|
14817
|
+
if (!line.trim() || line.trim().startsWith("#")) continue;
|
|
14818
|
+
const indented = /^\s/.test(line);
|
|
14819
|
+
if (indented && pendingKey) {
|
|
14820
|
+
if (line.trim().startsWith("- ")) {
|
|
14821
|
+
list.push(stripQuotes(line.trim().slice(2).trim()));
|
|
14822
|
+
} else {
|
|
14823
|
+
const nested = splitKey(line);
|
|
14824
|
+
if (nested) map[nested.key] = stripQuotes(nested.value);
|
|
14825
|
+
else errors.push(`Cannot read indented line: ${line.trim()}`);
|
|
14826
|
+
}
|
|
14827
|
+
continue;
|
|
14828
|
+
}
|
|
14829
|
+
if (indented) {
|
|
14830
|
+
errors.push(`Cannot read indented line: ${line.trim()}`);
|
|
14831
|
+
continue;
|
|
14832
|
+
}
|
|
14833
|
+
flush();
|
|
14834
|
+
const pair = splitKey(line);
|
|
14835
|
+
if (!pair) {
|
|
14836
|
+
errors.push(`Cannot read line: ${line.trim()}`);
|
|
14837
|
+
continue;
|
|
14838
|
+
}
|
|
14839
|
+
if (pair.value === "") {
|
|
14840
|
+
pendingKey = pair.key;
|
|
14841
|
+
continue;
|
|
14842
|
+
}
|
|
14843
|
+
fields[pair.key] = stripQuotes(pair.value);
|
|
14844
|
+
}
|
|
14845
|
+
flush();
|
|
14846
|
+
const tools = fields["allowed-tools"];
|
|
14847
|
+
if (typeof tools === "string") {
|
|
14848
|
+
fields["allowed-tools"] = tools.split(",").map((tool) => tool.trim()).filter(Boolean);
|
|
14849
|
+
}
|
|
14850
|
+
if (fields["disable-model-invocation"] === "true") {
|
|
14851
|
+
errors.push("disable-model-invocation must be false.");
|
|
14852
|
+
delete fields["disable-model-invocation"];
|
|
14853
|
+
}
|
|
14854
|
+
if (fields["disable-model-invocation"] === "false") {
|
|
14855
|
+
fields["disable-model-invocation"] = false;
|
|
14856
|
+
}
|
|
14857
|
+
const body = lines.slice(end + 1).join("\n").replace(/^\n/, "");
|
|
14858
|
+
const parsed = skillFrontmatterSchema.safeParse(fields);
|
|
14859
|
+
if (!parsed.success) {
|
|
14860
|
+
for (const issue of parsed.error.issues) {
|
|
14861
|
+
errors.push(`${issue.path.join(".") || "frontmatter"}: ${issue.message}`);
|
|
14862
|
+
}
|
|
14863
|
+
return {
|
|
14864
|
+
frontmatter: {
|
|
14865
|
+
name: typeof fields.name === "string" ? fields.name : "",
|
|
14866
|
+
description: typeof fields.description === "string" ? fields.description : ""
|
|
14867
|
+
},
|
|
14868
|
+
body,
|
|
14869
|
+
errors
|
|
14870
|
+
};
|
|
14871
|
+
}
|
|
14872
|
+
return { frontmatter: parsed.data, body, errors };
|
|
14873
|
+
}
|
|
14874
|
+
function resolveSkillDir(root, name) {
|
|
14875
|
+
if (!SKILL_NAME_REGEX.test(name) || name === "." || name === "..") {
|
|
14876
|
+
throw new Error(`Invalid skill name: ${name}`);
|
|
14877
|
+
}
|
|
14878
|
+
return `${root.replace(/[/\\]+$/, "")}/${name}`;
|
|
14879
|
+
}
|
|
14880
|
+
|
|
14881
|
+
// ../../shared/all/helpers/board-agent-core/host-skills.ts
|
|
14882
|
+
var AGENTS_ROOT = ".agents/skills";
|
|
14883
|
+
var CLAUDE_ROOT = ".claude/skills";
|
|
14884
|
+
var CLI_ROOTS = [CLAUDE_ROOT, ".codex/skills"];
|
|
14885
|
+
function globalAgentsRoot() {
|
|
14886
|
+
return import_path5.default.join(import_os2.default.homedir(), ".agents", "skills");
|
|
14887
|
+
}
|
|
14888
|
+
var AGENTS_LINK_TARGET = "../.agents/skills";
|
|
14889
|
+
var BRANCH_PREFIX = "skill/";
|
|
14890
|
+
var SYMLINK_MODE = "120000";
|
|
14891
|
+
function summarize(raw, base) {
|
|
14892
|
+
const { frontmatter, errors } = parseSkill(raw);
|
|
14893
|
+
return { ...base, description: frontmatter.description, errors };
|
|
14894
|
+
}
|
|
14895
|
+
async function rootLinkState(root) {
|
|
14896
|
+
const linkPath = import_path5.default.join(import_os2.default.homedir(), ...root.split("/"));
|
|
14897
|
+
const stat = await import_promises3.default.lstat(linkPath).catch(() => null);
|
|
14898
|
+
if (!stat) return "missing";
|
|
14899
|
+
if (!stat.isSymbolicLink()) return "blocked";
|
|
14900
|
+
const target = await import_promises3.default.readlink(linkPath).catch(() => "");
|
|
14901
|
+
return target.replace(/\/+$/, "") === AGENTS_LINK_TARGET ? "linked" : "missing";
|
|
14902
|
+
}
|
|
14903
|
+
async function globalLinkState() {
|
|
14904
|
+
const states = await Promise.all(CLI_ROOTS.map(rootLinkState));
|
|
14905
|
+
if (states.includes("blocked")) return "blocked";
|
|
14906
|
+
return states.includes("missing") ? "missing" : "linked";
|
|
14907
|
+
}
|
|
14908
|
+
async function listGlobal() {
|
|
14909
|
+
const root = globalAgentsRoot();
|
|
14910
|
+
const entries = await import_promises3.default.readdir(root, { withFileTypes: true }).catch(() => []);
|
|
14911
|
+
const out = [];
|
|
14912
|
+
for (const entry of entries) {
|
|
14913
|
+
if (entry.isFile()) continue;
|
|
14914
|
+
const dir = import_path5.default.join(root, entry.name);
|
|
14915
|
+
const raw = await import_promises3.default.readFile(import_path5.default.join(dir, "SKILL.md"), "utf-8").catch(() => "");
|
|
14916
|
+
if (!raw) continue;
|
|
14917
|
+
out.push(
|
|
14918
|
+
summarize(raw, {
|
|
14919
|
+
name: entry.name,
|
|
14920
|
+
scope: "global",
|
|
14921
|
+
dir,
|
|
14922
|
+
status: "live",
|
|
14923
|
+
pendingBranch: ""
|
|
14924
|
+
})
|
|
14925
|
+
);
|
|
14926
|
+
}
|
|
14927
|
+
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
14928
|
+
}
|
|
14929
|
+
function strayRepoSkills(liveBlobs) {
|
|
14930
|
+
return [...liveBlobs.keys()].filter((key) => CLI_ROOTS.some((root) => key.startsWith(`${root}/`))).sort();
|
|
14931
|
+
}
|
|
14932
|
+
async function treeAt(repo, ref) {
|
|
14933
|
+
const raw = await hostGit(
|
|
14934
|
+
repo,
|
|
14935
|
+
"ls-tree",
|
|
14936
|
+
"-r",
|
|
14937
|
+
ref,
|
|
14938
|
+
"--",
|
|
14939
|
+
AGENTS_ROOT,
|
|
14940
|
+
...CLI_ROOTS
|
|
14941
|
+
).catch(() => "");
|
|
14942
|
+
return raw.split("\n").filter(Boolean).map((line) => {
|
|
14943
|
+
const [meta, filePath] = line.split(" ");
|
|
14944
|
+
const [mode, , sha] = meta.split(/\s+/);
|
|
14945
|
+
return { mode, sha, filePath };
|
|
14946
|
+
}).filter((entry) => Boolean(entry.filePath));
|
|
14947
|
+
}
|
|
14948
|
+
function skillBlobs(entries) {
|
|
14949
|
+
const blobs = /* @__PURE__ */ new Map();
|
|
14950
|
+
for (const entry of entries) {
|
|
14951
|
+
const match = /^(.+\/skills)\/([^/]+)\/SKILL\.md$/.exec(entry.filePath);
|
|
14952
|
+
if (match) blobs.set(`${match[1]}/${match[2]}`, entry.sha);
|
|
14953
|
+
}
|
|
14954
|
+
return blobs;
|
|
14955
|
+
}
|
|
14956
|
+
async function skillLinks(repo, entries) {
|
|
14957
|
+
const links = /* @__PURE__ */ new Map();
|
|
14958
|
+
const linkEntries = entries.filter(
|
|
14959
|
+
(entry) => entry.mode === SYMLINK_MODE && /^.+\/skills(\/[^/]+)?$/.test(entry.filePath)
|
|
14960
|
+
);
|
|
14961
|
+
const targets = await gitCatFileBatch(
|
|
14962
|
+
repo,
|
|
14963
|
+
linkEntries.map((entry) => entry.sha)
|
|
14964
|
+
);
|
|
14965
|
+
for (const entry of linkEntries) {
|
|
14966
|
+
const target = targets.get(entry.sha)?.trim();
|
|
14967
|
+
if (target) links.set(entry.filePath, target);
|
|
14968
|
+
}
|
|
14969
|
+
return links;
|
|
14970
|
+
}
|
|
14971
|
+
function resolveRepoSkills(liveBlobs, pending) {
|
|
14972
|
+
const keys = /* @__PURE__ */ new Set();
|
|
14973
|
+
for (const key of liveBlobs.keys()) {
|
|
14974
|
+
if (key.startsWith(`${AGENTS_ROOT}/`)) keys.add(key);
|
|
14975
|
+
}
|
|
14976
|
+
for (const entry of pending) {
|
|
14977
|
+
for (const key of entry.blobs.keys()) {
|
|
14978
|
+
if (key.startsWith(`${AGENTS_ROOT}/`)) keys.add(key);
|
|
14979
|
+
}
|
|
14980
|
+
}
|
|
14981
|
+
const out = [];
|
|
14982
|
+
for (const key of [...keys].sort()) {
|
|
14983
|
+
const name = key.slice(AGENTS_ROOT.length + 1);
|
|
14984
|
+
const live = liveBlobs.get(key) || "";
|
|
14985
|
+
const onBranch = pending.find((entry) => {
|
|
14986
|
+
const sha = entry.blobs.get(key);
|
|
14987
|
+
return Boolean(sha) && sha !== live;
|
|
14988
|
+
});
|
|
14989
|
+
const status = live ? onBranch ? "pending-edit" : "live" : "pending-new";
|
|
14990
|
+
out.push({
|
|
14991
|
+
name,
|
|
14992
|
+
dir: key,
|
|
14993
|
+
status,
|
|
14994
|
+
pendingBranch: status === "live" ? "" : onBranch?.branch || "",
|
|
14995
|
+
sha: live || onBranch?.blobs.get(key) || ""
|
|
14996
|
+
});
|
|
14997
|
+
}
|
|
14998
|
+
return out;
|
|
14999
|
+
}
|
|
15000
|
+
async function repoSkills(ctx) {
|
|
15001
|
+
const skills = resolveRepoSkills(ctx.liveBlobs, ctx.pending);
|
|
15002
|
+
const cache2 = /* @__PURE__ */ new Map();
|
|
15003
|
+
const missing = skills.map((skill) => skill.sha).filter((sha) => sha && !cache2.has(sha));
|
|
15004
|
+
for (const [sha, text] of await gitCatFileBatch(ctx.repo, missing)) {
|
|
15005
|
+
cache2.set(sha, text);
|
|
15006
|
+
}
|
|
15007
|
+
return skills.map(
|
|
15008
|
+
(skill) => summarize(cache2.get(skill.sha) ?? "", {
|
|
15009
|
+
name: skill.name,
|
|
15010
|
+
scope: "repo",
|
|
15011
|
+
dir: skill.dir,
|
|
15012
|
+
status: skill.status,
|
|
15013
|
+
pendingBranch: skill.pendingBranch
|
|
15014
|
+
})
|
|
15015
|
+
);
|
|
15016
|
+
}
|
|
15017
|
+
async function pendingBranches(repo, mainBranch) {
|
|
15018
|
+
const refs = await hostGit(
|
|
15019
|
+
repo,
|
|
15020
|
+
"for-each-ref",
|
|
15021
|
+
"--sort=-committerdate",
|
|
15022
|
+
"--format=%(refname:short)",
|
|
15023
|
+
`refs/remotes/origin/${BRANCH_PREFIX}*`
|
|
15024
|
+
).catch(() => "");
|
|
15025
|
+
const branches = await Promise.all(
|
|
15026
|
+
refs.split("\n").filter(Boolean).map(async (ref) => {
|
|
15027
|
+
const merged = await hostGit(
|
|
15028
|
+
repo,
|
|
15029
|
+
"merge-base",
|
|
15030
|
+
"--is-ancestor",
|
|
15031
|
+
ref,
|
|
15032
|
+
`origin/${mainBranch}`
|
|
15033
|
+
).then(
|
|
15034
|
+
() => true,
|
|
15035
|
+
() => false
|
|
15036
|
+
);
|
|
15037
|
+
if (merged) return null;
|
|
15038
|
+
return {
|
|
15039
|
+
branch: ref.replace(/^origin\//, ""),
|
|
15040
|
+
blobs: skillBlobs(await treeAt(repo, ref))
|
|
15041
|
+
};
|
|
15042
|
+
})
|
|
15043
|
+
);
|
|
15044
|
+
return branches.filter((entry) => entry !== null);
|
|
15045
|
+
}
|
|
15046
|
+
function requireRepo(opts) {
|
|
15047
|
+
if (!opts.repoUrl || !opts.githubToken) {
|
|
15048
|
+
throw new Error("Set the repository URL and GitHub token first.");
|
|
15049
|
+
}
|
|
15050
|
+
return {
|
|
15051
|
+
spaceDir: opts.spaceDir,
|
|
15052
|
+
repoUrl: opts.repoUrl,
|
|
15053
|
+
mainBranch: opts.mainBranch,
|
|
15054
|
+
githubToken: opts.githubToken
|
|
15055
|
+
};
|
|
15056
|
+
}
|
|
15057
|
+
async function ensureSkillsRepo(opts) {
|
|
15058
|
+
const { spaceDir, repoUrl, mainBranch, githubToken } = requireRepo(opts);
|
|
15059
|
+
const paths = hostSpacePaths(spaceDir);
|
|
15060
|
+
const branch = (mainBranch || "main").replace(/[^\w./-]/g, "");
|
|
15061
|
+
const url2 = buildCloneUrl(repoUrl, githubToken);
|
|
15062
|
+
try {
|
|
15063
|
+
await hostGit(paths.repo, "rev-parse", "--git-dir");
|
|
15064
|
+
await hostGit(paths.repo, "remote", "set-url", "origin", url2);
|
|
15065
|
+
} catch {
|
|
15066
|
+
await hostGit(paths.root, "clone", "--branch", branch, url2, paths.repo);
|
|
15067
|
+
}
|
|
15068
|
+
await hostGit(
|
|
15069
|
+
paths.repo,
|
|
15070
|
+
"fetch",
|
|
15071
|
+
"origin",
|
|
15072
|
+
branch,
|
|
15073
|
+
`+refs/heads/${BRANCH_PREFIX}*:refs/remotes/origin/${BRANCH_PREFIX}*`
|
|
15074
|
+
);
|
|
15075
|
+
return { repo: paths.repo, branch };
|
|
15076
|
+
}
|
|
15077
|
+
async function ensureSkillsWorktree(spaceDir, branch) {
|
|
15078
|
+
const { repo, skills: worktree } = hostSpacePaths(spaceDir);
|
|
15079
|
+
try {
|
|
15080
|
+
await hostGit(worktree, "rev-parse", "--git-dir");
|
|
15081
|
+
} catch {
|
|
15082
|
+
await hostGit(repo, "worktree", "prune").catch(() => {
|
|
15083
|
+
});
|
|
15084
|
+
await import_promises3.default.rm(worktree, { recursive: true, force: true });
|
|
15085
|
+
await hostGit(
|
|
15086
|
+
repo,
|
|
15087
|
+
"worktree",
|
|
15088
|
+
"add",
|
|
15089
|
+
"--detach",
|
|
15090
|
+
worktree,
|
|
15091
|
+
`origin/${branch}`
|
|
15092
|
+
);
|
|
15093
|
+
}
|
|
15094
|
+
return worktree;
|
|
15095
|
+
}
|
|
15096
|
+
async function repoContext(opts) {
|
|
15097
|
+
const { repo, branch } = await ensureSkillsRepo(opts);
|
|
15098
|
+
const liveTree = await treeAt(repo, `origin/${branch}`);
|
|
15099
|
+
const [links, pending] = await Promise.all([
|
|
15100
|
+
skillLinks(repo, liveTree),
|
|
15101
|
+
pendingBranches(repo, branch)
|
|
15102
|
+
]);
|
|
15103
|
+
return {
|
|
15104
|
+
repo,
|
|
15105
|
+
branch,
|
|
15106
|
+
liveTree,
|
|
15107
|
+
liveBlobs: skillBlobs(liveTree),
|
|
15108
|
+
links,
|
|
15109
|
+
pending
|
|
15110
|
+
};
|
|
15111
|
+
}
|
|
15112
|
+
function findRepoSkill(ctx, name) {
|
|
15113
|
+
return resolveRepoSkills(ctx.liveBlobs, ctx.pending).find(
|
|
15114
|
+
(skill) => skill.name === name
|
|
15115
|
+
) ?? null;
|
|
15116
|
+
}
|
|
15117
|
+
var workspaceQueues = /* @__PURE__ */ new Map();
|
|
15118
|
+
function withWorkspace(spaceDir, run2) {
|
|
15119
|
+
const next = (workspaceQueues.get(spaceDir) ?? Promise.resolve()).then(
|
|
15120
|
+
run2,
|
|
15121
|
+
run2
|
|
15122
|
+
);
|
|
15123
|
+
workspaceQueues.set(
|
|
15124
|
+
spaceDir,
|
|
15125
|
+
next.catch(() => {
|
|
15126
|
+
})
|
|
15127
|
+
);
|
|
15128
|
+
return next;
|
|
15129
|
+
}
|
|
15130
|
+
function repoLinkState(ctx) {
|
|
15131
|
+
const target = ctx.links.get(CLAUDE_ROOT);
|
|
15132
|
+
if (target) return target === AGENTS_LINK_TARGET ? "linked" : "missing";
|
|
15133
|
+
return [...ctx.liveBlobs.keys()].some(
|
|
15134
|
+
(key) => key.startsWith(`${CLAUDE_ROOT}/`)
|
|
15135
|
+
) ? "blocked" : "missing";
|
|
15136
|
+
}
|
|
15137
|
+
async function listSkillsImpl(opts) {
|
|
15138
|
+
const [global2, globalLink] = await Promise.all([
|
|
15139
|
+
listGlobal(),
|
|
15140
|
+
globalLinkState()
|
|
15141
|
+
]);
|
|
15142
|
+
if (!opts.repoUrl || !opts.githubToken) {
|
|
15143
|
+
return {
|
|
15144
|
+
global: global2,
|
|
15145
|
+
repo: [],
|
|
15146
|
+
globalLink,
|
|
15147
|
+
repoLink: "linked",
|
|
15148
|
+
repoBranch: "",
|
|
15149
|
+
repoAvailable: false
|
|
15150
|
+
};
|
|
15151
|
+
}
|
|
15152
|
+
const ctx = await repoContext(opts);
|
|
15153
|
+
const repo = await repoSkills(ctx);
|
|
15154
|
+
return {
|
|
15155
|
+
global: global2,
|
|
15156
|
+
repo,
|
|
15157
|
+
globalLink,
|
|
15158
|
+
repoLink: repoLinkState(ctx),
|
|
15159
|
+
repoBranch: ctx.branch,
|
|
15160
|
+
repoAvailable: true
|
|
15161
|
+
};
|
|
15162
|
+
}
|
|
15163
|
+
async function bundleFilesOnDisk(dir) {
|
|
15164
|
+
const out = [];
|
|
15165
|
+
const walk = async (current, prefix, depth) => {
|
|
15166
|
+
if (depth > 3 || out.length >= 200) return;
|
|
15167
|
+
const entries = await import_promises3.default.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
15168
|
+
for (const entry of entries) {
|
|
15169
|
+
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
15170
|
+
if (entry.isDirectory()) {
|
|
15171
|
+
await walk(import_path5.default.join(current, entry.name), rel, depth + 1);
|
|
15172
|
+
} else if (rel !== "SKILL.md") {
|
|
15173
|
+
out.push(rel);
|
|
15174
|
+
}
|
|
15175
|
+
}
|
|
15176
|
+
};
|
|
15177
|
+
await walk(dir, "", 0);
|
|
15178
|
+
return out.sort();
|
|
15179
|
+
}
|
|
15180
|
+
async function readSkillImpl(opts) {
|
|
15181
|
+
if (opts.scope === "global") {
|
|
15182
|
+
const dir = resolveSkillDir(globalAgentsRoot(), opts.name);
|
|
15183
|
+
const content2 = await import_promises3.default.readFile(import_path5.default.join(dir, "SKILL.md"), "utf-8");
|
|
15184
|
+
const { frontmatter: frontmatter2, errors: errors2 } = parseSkill(content2);
|
|
15185
|
+
return {
|
|
15186
|
+
name: opts.name,
|
|
15187
|
+
scope: "global",
|
|
15188
|
+
content: content2,
|
|
15189
|
+
frontmatter: frontmatter2,
|
|
15190
|
+
errors: errors2,
|
|
15191
|
+
bundleFiles: await bundleFilesOnDisk(dir),
|
|
15192
|
+
status: "live",
|
|
15193
|
+
pendingBranch: ""
|
|
15194
|
+
};
|
|
15195
|
+
}
|
|
15196
|
+
const ctx = await repoContext(opts);
|
|
15197
|
+
const skill = findRepoSkill(ctx, opts.name);
|
|
15198
|
+
if (!skill) throw new Error(`No repo skill named ${opts.name}.`);
|
|
15199
|
+
const draft = skill.pendingBranch ? ctx.pending.find((entry) => entry.branch === skill.pendingBranch)?.blobs.get(skill.dir) : "";
|
|
15200
|
+
const content = await hostGit(ctx.repo, "cat-file", "-p", draft || skill.sha);
|
|
15201
|
+
const { frontmatter, errors } = parseSkill(content);
|
|
15202
|
+
const tree = draft ? await treeAt(ctx.repo, `origin/${skill.pendingBranch}`) : ctx.liveTree;
|
|
15203
|
+
const bundleFiles = tree.filter(
|
|
15204
|
+
(entry) => entry.filePath.startsWith(`${skill.dir}/`) && entry.filePath !== `${skill.dir}/SKILL.md`
|
|
15205
|
+
).map((entry) => entry.filePath.slice(skill.dir.length + 1)).sort();
|
|
15206
|
+
return {
|
|
15207
|
+
name: skill.name,
|
|
15208
|
+
scope: "repo",
|
|
15209
|
+
content,
|
|
15210
|
+
frontmatter,
|
|
15211
|
+
errors,
|
|
15212
|
+
bundleFiles,
|
|
15213
|
+
status: skill.status,
|
|
15214
|
+
pendingBranch: skill.pendingBranch
|
|
15215
|
+
};
|
|
15216
|
+
}
|
|
15217
|
+
async function moveIntoAgents(from, to) {
|
|
15218
|
+
if (await import_promises3.default.access(to).then(
|
|
15219
|
+
() => true,
|
|
15220
|
+
() => false
|
|
15221
|
+
)) {
|
|
15222
|
+
throw new Error(
|
|
15223
|
+
`${import_path5.default.basename(to)} exists in both roots. Remove one copy, then migrate.`
|
|
15224
|
+
);
|
|
15225
|
+
}
|
|
15226
|
+
await import_promises3.default.mkdir(import_path5.default.dirname(to), { recursive: true });
|
|
15227
|
+
try {
|
|
15228
|
+
await import_promises3.default.rename(from, to);
|
|
15229
|
+
} catch {
|
|
15230
|
+
await import_promises3.default.cp(from, to, { recursive: true });
|
|
15231
|
+
await import_promises3.default.rm(from, { recursive: true, force: true });
|
|
15232
|
+
}
|
|
15233
|
+
}
|
|
15234
|
+
async function migrateGlobal() {
|
|
15235
|
+
const moved = [];
|
|
15236
|
+
for (const root of CLI_ROOTS) {
|
|
15237
|
+
const dir = import_path5.default.join(import_os2.default.homedir(), ...root.split("/"));
|
|
15238
|
+
const stat = await import_promises3.default.lstat(dir).catch(() => null);
|
|
15239
|
+
if (!stat || stat.isSymbolicLink()) continue;
|
|
15240
|
+
const entries = await import_promises3.default.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
15241
|
+
for (const entry of entries) {
|
|
15242
|
+
if (entry.isFile() || entry.isSymbolicLink()) continue;
|
|
15243
|
+
const src = import_path5.default.join(dir, entry.name);
|
|
15244
|
+
if (!await import_promises3.default.access(import_path5.default.join(src, "SKILL.md")).then(
|
|
15245
|
+
() => true,
|
|
15246
|
+
() => false
|
|
15247
|
+
)) {
|
|
15248
|
+
continue;
|
|
15249
|
+
}
|
|
15250
|
+
await moveIntoAgents(src, import_path5.default.join(globalAgentsRoot(), entry.name));
|
|
15251
|
+
moved.push(entry.name);
|
|
15252
|
+
}
|
|
15253
|
+
await import_promises3.default.rmdir(dir).catch(() => {
|
|
15254
|
+
});
|
|
15255
|
+
}
|
|
15256
|
+
for (const root of CLI_ROOTS) {
|
|
15257
|
+
const linkPath = import_path5.default.join(import_os2.default.homedir(), ...root.split("/"));
|
|
15258
|
+
const after = await import_promises3.default.lstat(linkPath).catch(() => null);
|
|
15259
|
+
if (after?.isSymbolicLink()) await import_promises3.default.rm(linkPath, { force: true });
|
|
15260
|
+
else if (after) {
|
|
15261
|
+
throw new Error(
|
|
15262
|
+
`~/${root} still has files that are not skills. Empty it, then migrate.`
|
|
15263
|
+
);
|
|
15264
|
+
}
|
|
15265
|
+
await import_promises3.default.mkdir(import_path5.default.dirname(linkPath), { recursive: true });
|
|
15266
|
+
await import_promises3.default.symlink(AGENTS_LINK_TARGET, linkPath, "dir");
|
|
15267
|
+
}
|
|
15268
|
+
return moved;
|
|
15269
|
+
}
|
|
15270
|
+
async function migrateSkillsImpl(opts) {
|
|
15271
|
+
if (opts.scope === "global") {
|
|
15272
|
+
return { branch: "", moved: await migrateGlobal() };
|
|
15273
|
+
}
|
|
15274
|
+
const ctx = await repoContext(opts);
|
|
15275
|
+
const strays = strayRepoSkills(ctx.liveBlobs);
|
|
15276
|
+
const linked = ctx.links.get(CLAUDE_ROOT);
|
|
15277
|
+
if (!strays.length && linked === AGENTS_LINK_TARGET) {
|
|
15278
|
+
return { branch: "", moved: [] };
|
|
15279
|
+
}
|
|
15280
|
+
const { branch } = ctx;
|
|
15281
|
+
const workspace = await ensureSkillsWorktree(opts.spaceDir, branch);
|
|
15282
|
+
const target = `${BRANCH_PREFIX}migrate`;
|
|
15283
|
+
await hostGit(workspace, "checkout", "-B", target, `origin/${branch}`);
|
|
15284
|
+
await hostGit(workspace, "clean", "-fd");
|
|
15285
|
+
if (opts.gitName) {
|
|
15286
|
+
await hostGit(workspace, "config", "user.name", opts.gitName);
|
|
15287
|
+
}
|
|
15288
|
+
if (opts.gitEmail) {
|
|
15289
|
+
await hostGit(workspace, "config", "user.email", opts.gitEmail);
|
|
15290
|
+
}
|
|
15291
|
+
const moved = [];
|
|
15292
|
+
for (const stray of strays) {
|
|
15293
|
+
const name = stray.slice(stray.lastIndexOf("/") + 1);
|
|
15294
|
+
const dest = `${AGENTS_ROOT}/${name}`;
|
|
15295
|
+
if (ctx.liveBlobs.has(dest)) {
|
|
15296
|
+
throw new Error(
|
|
15297
|
+
`${name} exists in both ${AGENTS_ROOT} and ${stray}. Remove one copy, then migrate.`
|
|
15298
|
+
);
|
|
15299
|
+
}
|
|
15300
|
+
await import_promises3.default.mkdir(import_path5.default.join(workspace, AGENTS_ROOT), { recursive: true });
|
|
15301
|
+
await hostGit(workspace, "mv", stray, dest);
|
|
15302
|
+
moved.push(name);
|
|
15303
|
+
}
|
|
15304
|
+
const linkPath = import_path5.default.join(workspace, CLAUDE_ROOT);
|
|
15305
|
+
await import_promises3.default.rm(linkPath, { recursive: true, force: true });
|
|
15306
|
+
await import_promises3.default.symlink(AGENTS_LINK_TARGET, linkPath, "dir");
|
|
15307
|
+
await hostGit(workspace, "add", "-A");
|
|
15308
|
+
const dirty = await hostGit(workspace, "status", "--porcelain");
|
|
15309
|
+
if (!dirty.trim()) return { branch: "", moved: [] };
|
|
15310
|
+
await hostGit(
|
|
15311
|
+
workspace,
|
|
15312
|
+
"commit",
|
|
15313
|
+
"-m",
|
|
15314
|
+
`Migrate ${moved.length} skill(s) to ${AGENTS_ROOT} and link ${CLAUDE_ROOT}`
|
|
15315
|
+
);
|
|
15316
|
+
await hostGit(workspace, "push", "origin", target);
|
|
15317
|
+
return { branch: target, moved };
|
|
15318
|
+
}
|
|
15319
|
+
function assertWritable(name, content) {
|
|
15320
|
+
const parsed = parseSkill(content);
|
|
15321
|
+
if (parsed.errors.length) {
|
|
15322
|
+
throw new Error(`Fix the skill first: ${parsed.errors.join(" ")}`);
|
|
15323
|
+
}
|
|
15324
|
+
if (parsed.frontmatter.name !== name) {
|
|
15325
|
+
throw new Error(
|
|
15326
|
+
`Frontmatter name (${parsed.frontmatter.name}) must match the skill directory (${name}).`
|
|
15327
|
+
);
|
|
15328
|
+
}
|
|
15329
|
+
}
|
|
15330
|
+
async function writeSkillFile(opts) {
|
|
15331
|
+
const dir = resolveSkillDir(opts.root, opts.name);
|
|
15332
|
+
const file = import_path5.default.join(dir, "SKILL.md");
|
|
15333
|
+
if (opts.keepExisting) {
|
|
15334
|
+
const exists = await import_promises3.default.access(file).then(
|
|
15335
|
+
() => true,
|
|
15336
|
+
() => false
|
|
15337
|
+
);
|
|
15338
|
+
if (exists) return;
|
|
15339
|
+
}
|
|
15340
|
+
await import_promises3.default.mkdir(dir, { recursive: true });
|
|
15341
|
+
await import_promises3.default.writeFile(file, opts.content, "utf-8");
|
|
15342
|
+
}
|
|
15343
|
+
async function writeGlobalSkill(opts) {
|
|
15344
|
+
assertWritable(opts.name, opts.content);
|
|
15345
|
+
await writeSkillFile({ root: globalAgentsRoot(), ...opts });
|
|
15346
|
+
}
|
|
15347
|
+
async function installBundledSkill(opts) {
|
|
15348
|
+
assertWritable(opts.name, opts.content);
|
|
15349
|
+
const home = opts.home ?? import_os2.default.homedir();
|
|
15350
|
+
const write = (root) => writeSkillFile({
|
|
15351
|
+
root,
|
|
15352
|
+
name: opts.name,
|
|
15353
|
+
content: opts.content,
|
|
15354
|
+
keepExisting: true
|
|
15355
|
+
});
|
|
15356
|
+
await write(import_path5.default.join(home, ...AGENTS_ROOT.split("/")));
|
|
15357
|
+
for (const root of CLI_ROOTS) {
|
|
15358
|
+
const linkPath = import_path5.default.join(home, ...root.split("/"));
|
|
15359
|
+
const stat = await import_promises3.default.lstat(linkPath).catch(() => null);
|
|
15360
|
+
if (stat?.isSymbolicLink()) continue;
|
|
15361
|
+
if (!stat) {
|
|
15362
|
+
await import_promises3.default.mkdir(import_path5.default.dirname(linkPath), { recursive: true });
|
|
15363
|
+
await import_promises3.default.symlink(AGENTS_LINK_TARGET, linkPath, "dir").catch(() => void 0);
|
|
15364
|
+
continue;
|
|
15365
|
+
}
|
|
15366
|
+
await write(linkPath);
|
|
15367
|
+
}
|
|
15368
|
+
}
|
|
15369
|
+
async function deleteGlobalSkill(opts) {
|
|
15370
|
+
const dir = resolveSkillDir(globalAgentsRoot(), opts.name);
|
|
15371
|
+
await import_promises3.default.rm(dir, { recursive: true, force: true });
|
|
15372
|
+
}
|
|
15373
|
+
async function proposeRepoSkillImpl(opts) {
|
|
15374
|
+
assertWritable(opts.name, opts.content);
|
|
15375
|
+
if (!SKILL_NAME_REGEX.test(opts.name)) {
|
|
15376
|
+
throw new Error(`Invalid skill name: ${opts.name}`);
|
|
15377
|
+
}
|
|
15378
|
+
const ctx = await repoContext(opts);
|
|
15379
|
+
const known = findRepoSkill(ctx, opts.name);
|
|
15380
|
+
const dir = known?.dir || `${AGENTS_ROOT}/${opts.name}`;
|
|
15381
|
+
const { branch } = ctx;
|
|
15382
|
+
const workspace = await ensureSkillsWorktree(opts.spaceDir, branch);
|
|
15383
|
+
const target = `${BRANCH_PREFIX}${opts.name}`;
|
|
15384
|
+
const existing = await hostGit(
|
|
15385
|
+
workspace,
|
|
15386
|
+
"ls-remote",
|
|
15387
|
+
"--heads",
|
|
15388
|
+
"origin",
|
|
15389
|
+
target
|
|
15390
|
+
).catch(() => "");
|
|
15391
|
+
if (existing.trim()) {
|
|
15392
|
+
await hostGit(
|
|
15393
|
+
workspace,
|
|
15394
|
+
"fetch",
|
|
15395
|
+
"origin",
|
|
15396
|
+
`+refs/heads/${target}:refs/remotes/origin/${target}`
|
|
15397
|
+
);
|
|
15398
|
+
await hostGit(workspace, "checkout", "-B", target, `origin/${target}`);
|
|
15399
|
+
} else {
|
|
15400
|
+
await hostGit(workspace, "checkout", "-B", target, `origin/${branch}`);
|
|
15401
|
+
}
|
|
15402
|
+
await hostGit(workspace, "clean", "-fd");
|
|
15403
|
+
if (opts.gitName) {
|
|
15404
|
+
await hostGit(workspace, "config", "user.name", opts.gitName);
|
|
15405
|
+
}
|
|
15406
|
+
if (opts.gitEmail) {
|
|
15407
|
+
await hostGit(workspace, "config", "user.email", opts.gitEmail);
|
|
15408
|
+
}
|
|
15409
|
+
await import_promises3.default.mkdir(import_path5.default.join(workspace, dir), { recursive: true });
|
|
15410
|
+
await import_promises3.default.writeFile(
|
|
15411
|
+
import_path5.default.join(workspace, dir, "SKILL.md"),
|
|
15412
|
+
opts.content,
|
|
15413
|
+
"utf-8"
|
|
15414
|
+
);
|
|
15415
|
+
const dirty = await hostGit(workspace, "status", "--porcelain");
|
|
15416
|
+
if (!dirty.trim()) return { branch: target };
|
|
15417
|
+
await hostGit(workspace, "add", "-A");
|
|
15418
|
+
await hostGit(
|
|
15419
|
+
workspace,
|
|
15420
|
+
"commit",
|
|
15421
|
+
"-m",
|
|
15422
|
+
`${known ? "Update" : "Add"} skill: ${opts.name}`
|
|
15423
|
+
);
|
|
15424
|
+
await hostGit(workspace, "push", "origin", target);
|
|
15425
|
+
return { branch: target };
|
|
15426
|
+
}
|
|
15427
|
+
function listSkills(opts) {
|
|
15428
|
+
return withWorkspace(opts.spaceDir, () => listSkillsImpl(opts));
|
|
15429
|
+
}
|
|
15430
|
+
function readSkill(opts) {
|
|
15431
|
+
return withWorkspace(opts.spaceDir, () => readSkillImpl(opts));
|
|
15432
|
+
}
|
|
15433
|
+
function migrateSkills(opts) {
|
|
15434
|
+
return withWorkspace(opts.spaceDir, () => migrateSkillsImpl(opts));
|
|
15435
|
+
}
|
|
15436
|
+
function proposeRepoSkill(opts) {
|
|
15437
|
+
return withWorkspace(opts.spaceDir, () => proposeRepoSkillImpl(opts));
|
|
15438
|
+
}
|
|
15439
|
+
|
|
15440
|
+
// ../../shared/all/helpers/card-ai/scripts.ts
|
|
15441
|
+
function frpcExposeScript(p) {
|
|
15442
|
+
return `#!/bin/sh
|
|
15443
|
+
set -e
|
|
15444
|
+
set -a; . '${p.envFile}' 2>/dev/null || true; set +a
|
|
15445
|
+
mkdir -p '${p.runDir}'
|
|
15446
|
+
PORT="$1"
|
|
15447
|
+
NAME="$2"
|
|
15448
|
+
if [ -z "$PORT" ] || [ -z "$NAME" ]; then
|
|
15449
|
+
echo "usage: factiii-expose <port> <name>" >&2
|
|
15450
|
+
echo " e.g. factiii-expose 3000 app" >&2
|
|
15451
|
+
exit 1
|
|
15452
|
+
fi
|
|
15453
|
+
case "$NAME" in *[!a-zA-Z0-9-]*|"") echo "name must be [a-zA-Z0-9-]" >&2; exit 1 ;; esac
|
|
15454
|
+
PROXY="\${FACTIII_TASK_ID}-\${NAME}"
|
|
15455
|
+
DOMAIN="\${PROXY}.\${FACTIII_FRP_DOMAIN}"
|
|
15456
|
+
TOML="${p.runDir}/\${NAME}.toml"
|
|
15457
|
+
cat > "$TOML" <<EOF
|
|
15458
|
+
serverAddr = "\${FACTIII_FRP_SERVER}"
|
|
15459
|
+
serverPort = \${FACTIII_FRP_PORT}
|
|
15460
|
+
transport.protocol = "tcp"
|
|
15461
|
+
|
|
15462
|
+
[metadatas]
|
|
15463
|
+
factiii_token = "\${FACTIII_FRP_TOKEN}"
|
|
15464
|
+
|
|
15465
|
+
[[proxies]]
|
|
15466
|
+
name = "\${PROXY}"
|
|
15467
|
+
type = "http"
|
|
15468
|
+
localPort = \${PORT}
|
|
15469
|
+
subdomain = "\${PROXY}"
|
|
15470
|
+
EOF
|
|
15471
|
+
# nohup, not setsid: setsid does not exist on macOS.
|
|
15472
|
+
nohup frpc -c "$TOML" > "${p.runDir}/\${NAME}.log" 2>&1 < /dev/null &
|
|
15473
|
+
echo $! > "${p.runDir}/\${NAME}.pid"
|
|
15474
|
+
echo "http://\${DOMAIN}"
|
|
15475
|
+
`;
|
|
15476
|
+
}
|
|
15477
|
+
function frpcUnexposeScript(p) {
|
|
15478
|
+
return `#!/bin/sh
|
|
15479
|
+
NAME="$1"
|
|
15480
|
+
if [ -z "$NAME" ]; then
|
|
15481
|
+
echo "usage: factiii-unexpose <name>" >&2
|
|
15482
|
+
exit 1
|
|
15483
|
+
fi
|
|
15484
|
+
PID_FILE="${p.runDir}/\${NAME}.pid"
|
|
15485
|
+
if [ -f "$PID_FILE" ]; then
|
|
15486
|
+
kill "$(cat "$PID_FILE")" 2>/dev/null || true
|
|
15487
|
+
rm -f "$PID_FILE" "${p.runDir}/\${NAME}.toml" "${p.runDir}/\${NAME}.log"
|
|
15488
|
+
echo "stopped \${NAME}"
|
|
15489
|
+
else
|
|
15490
|
+
echo "no tunnel named \${NAME}" >&2
|
|
15491
|
+
exit 1
|
|
15492
|
+
fi
|
|
15493
|
+
`;
|
|
15494
|
+
}
|
|
15495
|
+
|
|
15496
|
+
// ../../shared/all/helpers/card-ai/frpc.ts
|
|
15497
|
+
var FRP_SERVER_ADDR = "dev.factiii.com";
|
|
15498
|
+
var FRP_SERVER_PORT = 7443;
|
|
15499
|
+
var FRP_DOMAIN_SUFFIX = "dev.factiii.com";
|
|
15500
|
+
function frpcPaths(target) {
|
|
15501
|
+
const { root, bin } = target.paths;
|
|
15502
|
+
return {
|
|
15503
|
+
binDir: bin,
|
|
15504
|
+
binary: `${bin}/frpc`,
|
|
15505
|
+
expose: `${bin}/factiii-expose`,
|
|
15506
|
+
unexpose: `${bin}/factiii-unexpose`,
|
|
15507
|
+
envFile: `${root}/.factiii-frp-env`,
|
|
15508
|
+
runDir: `${root}/.frpc`
|
|
15509
|
+
};
|
|
15510
|
+
}
|
|
15511
|
+
function vendoredBinaryName() {
|
|
15512
|
+
const cpu = process.arch === "x64" ? "amd64" : process.arch;
|
|
15513
|
+
return `frpc-${process.platform}-${cpu}`;
|
|
15514
|
+
}
|
|
15515
|
+
async function installFrpcBinary(target) {
|
|
15516
|
+
const p = frpcPaths(target);
|
|
15517
|
+
const binaryName = vendoredBinaryName();
|
|
15518
|
+
const src = import_path6.default.join(__dirname, "..", "vendor", binaryName);
|
|
15519
|
+
if (!(0, import_fs2.existsSync)(src)) {
|
|
15520
|
+
throw new Error(
|
|
15521
|
+
`Vendored frpc missing (${src}). Run apps/runner/scripts/fetch-frpc.sh.`
|
|
15522
|
+
);
|
|
15523
|
+
}
|
|
15524
|
+
const staged = await import_promises4.default.stat(p.binary).catch(() => null);
|
|
15525
|
+
const source = await import_promises4.default.stat(src);
|
|
15526
|
+
if (staged?.size === source.size) return;
|
|
15527
|
+
await import_promises4.default.mkdir(p.binDir, { recursive: true });
|
|
15528
|
+
await import_promises4.default.copyFile(src, p.binary);
|
|
15529
|
+
await import_promises4.default.chmod(p.binary, 493);
|
|
15530
|
+
}
|
|
15531
|
+
var EXPOSE_SKILL_NAME = "share-a-port";
|
|
15532
|
+
var FENCE = "```";
|
|
15533
|
+
var EXPOSE_SKILL_MD = [
|
|
15534
|
+
"---",
|
|
15535
|
+
`name: ${EXPOSE_SKILL_NAME}`,
|
|
15536
|
+
"description: Give a port running in this workspace a public HTTPS URL, with factiii-expose. TRIGGER when the user asks to share, expose, preview or publish a local server, wants a link to something running on localhost, or needs an external service to reach a local port.",
|
|
15537
|
+
"---",
|
|
15538
|
+
"",
|
|
15539
|
+
`# ${EXPOSE_SKILL_NAME}`,
|
|
15540
|
+
"",
|
|
15541
|
+
"This workspace can put a local port on the public internet. Both commands are",
|
|
15542
|
+
"already on your PATH; nothing needs installing.",
|
|
15543
|
+
"",
|
|
15544
|
+
"## Share a port",
|
|
15545
|
+
"",
|
|
15546
|
+
FENCE + "bash",
|
|
15547
|
+
"factiii-expose <port> <name> # e.g. factiii-expose 3000 app",
|
|
15548
|
+
FENCE,
|
|
15549
|
+
"",
|
|
15550
|
+
"It prints the public URL and returns straight away - the tunnel runs in the",
|
|
15551
|
+
"background and lives until the session ends or you stop it.",
|
|
15552
|
+
"",
|
|
15553
|
+
"- `<name>` may hold letters, numbers and dashes only.",
|
|
15554
|
+
"- Re-running it with the same name replaces that tunnel rather than opening a",
|
|
15555
|
+
" second one, so it is safe to repeat.",
|
|
15556
|
+
"- Start the server FIRST. A URL for a port nothing is listening on answers",
|
|
15557
|
+
" with an error, which reads to the user like a broken tunnel.",
|
|
15558
|
+
"",
|
|
15559
|
+
"## Stop sharing",
|
|
15560
|
+
"",
|
|
15561
|
+
FENCE + "bash",
|
|
15562
|
+
"factiii-unexpose <name>",
|
|
15563
|
+
FENCE,
|
|
15564
|
+
"",
|
|
15565
|
+
"## Before you share",
|
|
15566
|
+
"",
|
|
15567
|
+
"The URL is public and unauthenticated: anyone holding the link reaches",
|
|
15568
|
+
"whatever that port serves. Do not expose a service that fronts real",
|
|
15569
|
+
"credentials, customer data, or a database. If you are unsure whether the user",
|
|
15570
|
+
"wants something reachable from outside, ask first.",
|
|
15571
|
+
"",
|
|
15572
|
+
"Always tell the user the URL you got back. They can also see every live",
|
|
15573
|
+
"tunnel, and stop any of them, in the card's Task Manager panel.",
|
|
15574
|
+
""
|
|
15575
|
+
].join("\n");
|
|
15576
|
+
async function installFrpcHarness(params) {
|
|
15577
|
+
const { target, taskId, runnerToken } = params;
|
|
15578
|
+
const p = frpcPaths(target);
|
|
15579
|
+
await installFrpcBinary(target);
|
|
15580
|
+
const envLines = [
|
|
15581
|
+
`FACTIII_FRP_SERVER=${FRP_SERVER_ADDR}`,
|
|
15582
|
+
`FACTIII_FRP_PORT=${FRP_SERVER_PORT}`,
|
|
15583
|
+
`FACTIII_FRP_TOKEN=${runnerToken}`,
|
|
15584
|
+
`FACTIII_FRP_DOMAIN=${FRP_DOMAIN_SUFFIX}`,
|
|
15585
|
+
`FACTIII_TASK_ID=${taskId}`
|
|
13772
15586
|
];
|
|
13773
15587
|
const scriptPaths = { envFile: p.envFile, runDir: p.runDir };
|
|
13774
15588
|
await target.writeFile(p.expose, frpcExposeScript(scriptPaths));
|
|
13775
15589
|
await target.writeFile(p.unexpose, frpcUnexposeScript(scriptPaths));
|
|
13776
15590
|
await target.sh(`chmod +x '${p.expose}' '${p.unexpose}'`);
|
|
13777
15591
|
await target.writeFile(p.envFile, envLines.join("\n") + "\n");
|
|
15592
|
+
await installBundledSkill({
|
|
15593
|
+
name: EXPOSE_SKILL_NAME,
|
|
15594
|
+
content: EXPOSE_SKILL_MD
|
|
15595
|
+
}).catch(() => void 0);
|
|
15596
|
+
}
|
|
15597
|
+
var ROW_SEP = "|";
|
|
15598
|
+
function parseTunnelRows(raw) {
|
|
15599
|
+
const tunnels = [];
|
|
15600
|
+
for (const line of raw.split("\n")) {
|
|
15601
|
+
const [name, port, url2] = line.trim().split(ROW_SEP);
|
|
15602
|
+
if (!name || !url2) continue;
|
|
15603
|
+
const localPort = Number(port);
|
|
15604
|
+
if (!Number.isInteger(localPort) || localPort <= 0) continue;
|
|
15605
|
+
tunnels.push({ name, port: localPort, url: url2 });
|
|
15606
|
+
}
|
|
15607
|
+
return tunnels;
|
|
15608
|
+
}
|
|
15609
|
+
async function listLiveTunnels(target) {
|
|
15610
|
+
const p = frpcPaths(target);
|
|
15611
|
+
const script = [
|
|
15612
|
+
`set -a; . '${p.envFile}' 2>/dev/null || true; set +a`,
|
|
15613
|
+
`cd '${p.runDir}' 2>/dev/null || exit 0`,
|
|
15614
|
+
"for pidfile in *.pid; do",
|
|
15615
|
+
' [ -e "$pidfile" ] || continue',
|
|
15616
|
+
' name=$(basename "$pidfile" .pid)',
|
|
15617
|
+
' pid=$(cat "$pidfile" 2>/dev/null)',
|
|
15618
|
+
' [ -n "$pid" ] || continue',
|
|
15619
|
+
// The tunnel is whatever frpc is still running; a stale pid file is not one.
|
|
15620
|
+
' kill -0 "$pid" 2>/dev/null || continue',
|
|
15621
|
+
' [ -f "$name.toml" ] || continue',
|
|
15622
|
+
` port=$(sed -n 's/^localPort = *//p' "$name.toml" | head -1)`,
|
|
15623
|
+
` sub=$(sed -n 's/^subdomain = *//p' "$name.toml" | tr -d '"' | head -1)`,
|
|
15624
|
+
' [ -n "$port" ] && [ -n "$sub" ] || continue',
|
|
15625
|
+
` echo "$name${ROW_SEP}$port${ROW_SEP}http://$sub.$FACTIII_FRP_DOMAIN"`,
|
|
15626
|
+
"done"
|
|
15627
|
+
].join("\n");
|
|
15628
|
+
const out = await target.run(["sh", "-c", script]).catch(() => "");
|
|
15629
|
+
return parseTunnelRows(out).sort((a, b) => a.port - b.port);
|
|
13778
15630
|
}
|
|
13779
15631
|
|
|
13780
15632
|
// ../../shared/all/helpers/card-ai/claude-mode.ts
|
|
@@ -14064,17 +15916,18 @@ var ClaudeModeEngine = class _ClaudeModeEngine {
|
|
|
14064
15916
|
await this.core.ensureSpaceWorkspace();
|
|
14065
15917
|
await assertAgentSignedIn(session.provider, this.core.target());
|
|
14066
15918
|
if (hasGit) {
|
|
14067
|
-
const { path:
|
|
15919
|
+
const { path: path17 } = await addWorktree(
|
|
14068
15920
|
this.core.target(),
|
|
14069
15921
|
session.taskId,
|
|
14070
|
-
baseBranch || config.mainBranch || "main"
|
|
15922
|
+
baseBranch || config.mainBranch || "main",
|
|
15923
|
+
"card"
|
|
14071
15924
|
);
|
|
14072
|
-
session.workspacePath =
|
|
15925
|
+
session.workspacePath = path17;
|
|
14073
15926
|
await setGitIdentity(
|
|
14074
15927
|
this.core.target(),
|
|
14075
15928
|
config.gitName,
|
|
14076
15929
|
config.gitEmail,
|
|
14077
|
-
|
|
15930
|
+
path17
|
|
14078
15931
|
);
|
|
14079
15932
|
this.emitter.addLog(session, "system", "Worktree ready.");
|
|
14080
15933
|
} else {
|
|
@@ -14296,17 +16149,18 @@ var ClaudeModeEngine = class _ClaudeModeEngine {
|
|
|
14296
16149
|
this.emitter.addLog(session, "system", "Preparing environment...");
|
|
14297
16150
|
await this.core.ensureSpaceWorkspace();
|
|
14298
16151
|
await assertAgentSignedIn(session.provider, this.core.target());
|
|
14299
|
-
const { path:
|
|
16152
|
+
const { path: path17 } = await addWorktree(
|
|
14300
16153
|
this.core.target(),
|
|
14301
16154
|
session.taskId,
|
|
14302
|
-
config.mainBranch || "main"
|
|
16155
|
+
config.mainBranch || "main",
|
|
16156
|
+
"card"
|
|
14303
16157
|
);
|
|
14304
|
-
session.workspacePath =
|
|
16158
|
+
session.workspacePath = path17;
|
|
14305
16159
|
await setGitIdentity(
|
|
14306
16160
|
this.core.target(),
|
|
14307
16161
|
config.gitName,
|
|
14308
16162
|
config.gitEmail,
|
|
14309
|
-
|
|
16163
|
+
path17
|
|
14310
16164
|
);
|
|
14311
16165
|
await this.putCard(session);
|
|
14312
16166
|
const patchDir = `${this.core.target().paths.root}/patches/${session.taskId}`;
|
|
@@ -14601,6 +16455,150 @@ async function startTmuxSession(opts) {
|
|
|
14601
16455
|
};
|
|
14602
16456
|
}
|
|
14603
16457
|
|
|
16458
|
+
// ../../shared/all/helpers/terminal-ai/processes.ts
|
|
16459
|
+
var PS_FORMAT = "pid=,ppid=,pcpu=,rss=,etime=,args=";
|
|
16460
|
+
var PS_FIELDS = 5;
|
|
16461
|
+
function parsePsTable(raw) {
|
|
16462
|
+
const rows = [];
|
|
16463
|
+
for (const line of raw.split("\n")) {
|
|
16464
|
+
const trimmed = line.trim();
|
|
16465
|
+
const parts2 = trimmed.split(/\s+/);
|
|
16466
|
+
if (parts2.length <= PS_FIELDS) continue;
|
|
16467
|
+
const [pid, ppid, cpu, rss, elapsed] = parts2;
|
|
16468
|
+
const pidNum = Number(pid);
|
|
16469
|
+
const ppidNum = Number(ppid);
|
|
16470
|
+
if (!Number.isInteger(pidNum) || !Number.isInteger(ppidNum)) continue;
|
|
16471
|
+
rows.push({
|
|
16472
|
+
pid: pidNum,
|
|
16473
|
+
ppid: ppidNum,
|
|
16474
|
+
cpu: Number(cpu) || 0,
|
|
16475
|
+
rssKb: Number(rss) || 0,
|
|
16476
|
+
elapsed: elapsed ?? "",
|
|
16477
|
+
args: parts2.slice(PS_FIELDS).join(" ")
|
|
16478
|
+
});
|
|
16479
|
+
}
|
|
16480
|
+
return rows;
|
|
16481
|
+
}
|
|
16482
|
+
function descendantsOf(rows, roots) {
|
|
16483
|
+
const children = /* @__PURE__ */ new Map();
|
|
16484
|
+
for (const row of rows) {
|
|
16485
|
+
const list = children.get(row.ppid);
|
|
16486
|
+
if (list) list.push(row);
|
|
16487
|
+
else children.set(row.ppid, [row]);
|
|
16488
|
+
}
|
|
16489
|
+
const found = [];
|
|
16490
|
+
const seen = new Set(roots);
|
|
16491
|
+
const stack = [...roots];
|
|
16492
|
+
while (stack.length) {
|
|
16493
|
+
const pid = stack.pop();
|
|
16494
|
+
for (const child of children.get(pid) ?? []) {
|
|
16495
|
+
if (seen.has(child.pid)) continue;
|
|
16496
|
+
seen.add(child.pid);
|
|
16497
|
+
found.push(child);
|
|
16498
|
+
stack.push(child.pid);
|
|
16499
|
+
}
|
|
16500
|
+
}
|
|
16501
|
+
return found.sort((a, b) => a.pid - b.pid);
|
|
16502
|
+
}
|
|
16503
|
+
function commandOf(args) {
|
|
16504
|
+
const first = args.trim().split(/\s+/)[0] ?? "";
|
|
16505
|
+
const base = first.split("/").pop() ?? first;
|
|
16506
|
+
return base || "process";
|
|
16507
|
+
}
|
|
16508
|
+
var SHELLS = /* @__PURE__ */ new Set([
|
|
16509
|
+
"bash",
|
|
16510
|
+
"zsh",
|
|
16511
|
+
"sh",
|
|
16512
|
+
"dash",
|
|
16513
|
+
"fish",
|
|
16514
|
+
"ksh",
|
|
16515
|
+
"tcsh",
|
|
16516
|
+
"csh",
|
|
16517
|
+
"login"
|
|
16518
|
+
]);
|
|
16519
|
+
function isShell(args) {
|
|
16520
|
+
const name = commandOf(args);
|
|
16521
|
+
return SHELLS.has(name.startsWith("-") ? name.slice(1) : name);
|
|
16522
|
+
}
|
|
16523
|
+
function processesForPane(rows, panePid) {
|
|
16524
|
+
const kids = descendantsOf(rows, [panePid]);
|
|
16525
|
+
const pane = rows.find((row) => row.pid === panePid);
|
|
16526
|
+
if (pane && !isShell(pane.args)) return [pane, ...kids];
|
|
16527
|
+
return kids;
|
|
16528
|
+
}
|
|
16529
|
+
function portOf(address) {
|
|
16530
|
+
const colon = address.lastIndexOf(":");
|
|
16531
|
+
if (colon === -1) return null;
|
|
16532
|
+
const port = Number(address.slice(colon + 1));
|
|
16533
|
+
return Number.isInteger(port) && port > 0 && port <= 65535 ? port : null;
|
|
16534
|
+
}
|
|
16535
|
+
function parseLsofSockets(raw) {
|
|
16536
|
+
const use = /* @__PURE__ */ new Map();
|
|
16537
|
+
let pid = 0;
|
|
16538
|
+
let address = "";
|
|
16539
|
+
const entry = () => {
|
|
16540
|
+
let found = use.get(pid);
|
|
16541
|
+
if (!found) {
|
|
16542
|
+
found = { ports: [], connections: 0 };
|
|
16543
|
+
use.set(pid, found);
|
|
16544
|
+
}
|
|
16545
|
+
return found;
|
|
16546
|
+
};
|
|
16547
|
+
for (const line of raw.split("\n")) {
|
|
16548
|
+
const kind = line[0];
|
|
16549
|
+
if (kind === "p") {
|
|
16550
|
+
pid = Number(line.slice(1)) || 0;
|
|
16551
|
+
address = "";
|
|
16552
|
+
continue;
|
|
16553
|
+
}
|
|
16554
|
+
if (!pid) continue;
|
|
16555
|
+
if (kind === "n") {
|
|
16556
|
+
address = line.slice(1);
|
|
16557
|
+
continue;
|
|
16558
|
+
}
|
|
16559
|
+
if (!line.startsWith("TST=")) continue;
|
|
16560
|
+
const state = line.slice(4);
|
|
16561
|
+
if (state === "LISTEN") {
|
|
16562
|
+
const port = portOf(address);
|
|
16563
|
+
if (port !== null && !entry().ports.includes(port)) {
|
|
16564
|
+
entry().ports.push(port);
|
|
16565
|
+
}
|
|
16566
|
+
} else if (state === "ESTABLISHED") {
|
|
16567
|
+
entry().connections += 1;
|
|
16568
|
+
}
|
|
16569
|
+
address = "";
|
|
16570
|
+
}
|
|
16571
|
+
return use;
|
|
16572
|
+
}
|
|
16573
|
+
function parseSsListen(raw) {
|
|
16574
|
+
const use = /* @__PURE__ */ new Map();
|
|
16575
|
+
for (const line of raw.split("\n")) {
|
|
16576
|
+
const address = line.trim().split(/\s+/)[3];
|
|
16577
|
+
if (!address) continue;
|
|
16578
|
+
const port = portOf(address);
|
|
16579
|
+
if (port === null) continue;
|
|
16580
|
+
for (const match of line.matchAll(/pid=(\d+)/g)) {
|
|
16581
|
+
const pid = Number(match[1]);
|
|
16582
|
+
if (!Number.isInteger(pid)) continue;
|
|
16583
|
+
const found = use.get(pid) ?? { ports: [], connections: 0 };
|
|
16584
|
+
if (!found.ports.includes(port)) found.ports.push(port);
|
|
16585
|
+
use.set(pid, found);
|
|
16586
|
+
}
|
|
16587
|
+
}
|
|
16588
|
+
return use;
|
|
16589
|
+
}
|
|
16590
|
+
async function socketsFor(target, pids) {
|
|
16591
|
+
if (pids.length === 0) return /* @__PURE__ */ new Map();
|
|
16592
|
+
const lsof = await target.run(["lsof", "-a", "-p", pids.join(","), "-nP", "-iTCP", "-F", "pnT"]).catch((err) => String(err.stdout ?? ""));
|
|
16593
|
+
if (lsof.trim()) return parseLsofSockets(lsof);
|
|
16594
|
+
const ss = await target.run(["ss", "-ltnpH"]).catch(() => "");
|
|
16595
|
+
if (!ss.trim()) return /* @__PURE__ */ new Map();
|
|
16596
|
+
const all = parseSsListen(ss);
|
|
16597
|
+
const wanted = new Set(pids);
|
|
16598
|
+
for (const pid of all.keys()) if (!wanted.has(pid)) all.delete(pid);
|
|
16599
|
+
return all;
|
|
16600
|
+
}
|
|
16601
|
+
|
|
14604
16602
|
// ../../shared/all/helpers/terminal-ai/watcher.ts
|
|
14605
16603
|
var PRUNED_DIRS = [
|
|
14606
16604
|
".git",
|
|
@@ -14625,6 +16623,15 @@ var MAX_BATCH = 200;
|
|
|
14625
16623
|
var FLUSH_MS = 120;
|
|
14626
16624
|
var SELF_WRITE_MS = 2e3;
|
|
14627
16625
|
var dynamicImport = new Function("s", "return import(s)");
|
|
16626
|
+
var importFs = new Function("s", "return import(s)");
|
|
16627
|
+
async function resolveReal(path17) {
|
|
16628
|
+
try {
|
|
16629
|
+
const { realpath } = await importFs("fs/promises");
|
|
16630
|
+
return await realpath(path17);
|
|
16631
|
+
} catch {
|
|
16632
|
+
return path17;
|
|
16633
|
+
}
|
|
16634
|
+
}
|
|
14628
16635
|
var loadSubscribe = null;
|
|
14629
16636
|
function getSubscribe() {
|
|
14630
16637
|
loadSubscribe ??= dynamicImport("@parcel/watcher").then((m) => m.subscribe).catch(() => null);
|
|
@@ -14656,6 +16663,7 @@ var WorkspaceWatcher = class {
|
|
|
14656
16663
|
* not come back as "changed on disk". */
|
|
14657
16664
|
this.selfWrites = /* @__PURE__ */ new Map();
|
|
14658
16665
|
this.closed = false;
|
|
16666
|
+
this.watchRoot = root;
|
|
14659
16667
|
}
|
|
14660
16668
|
/** Fire-and-forget: callers treat starting a watch as synchronous, and
|
|
14661
16669
|
* nothing can be emitted before `subscribe` resolves anyway. */
|
|
@@ -14666,9 +16674,11 @@ var WorkspaceWatcher = class {
|
|
|
14666
16674
|
async open() {
|
|
14667
16675
|
const subscribe = await getSubscribe();
|
|
14668
16676
|
if (!subscribe) return this.stop();
|
|
16677
|
+
this.watchRoot = await resolveReal(this.root);
|
|
16678
|
+
const exclude = await Promise.all(this.exclude.map(resolveReal));
|
|
14669
16679
|
try {
|
|
14670
16680
|
const sub = await subscribe(
|
|
14671
|
-
this.
|
|
16681
|
+
this.watchRoot,
|
|
14672
16682
|
(err, events) => {
|
|
14673
16683
|
if (err) return this.stop();
|
|
14674
16684
|
for (const event of events) this.onEvent(event);
|
|
@@ -14678,7 +16688,7 @@ var WorkspaceWatcher = class {
|
|
|
14678
16688
|
ignore: [
|
|
14679
16689
|
...PRUNED_DIRS.map((d) => `**/${d}/**`),
|
|
14680
16690
|
...NOISE_GLOBS,
|
|
14681
|
-
...
|
|
16691
|
+
...exclude
|
|
14682
16692
|
]
|
|
14683
16693
|
}
|
|
14684
16694
|
);
|
|
@@ -14707,8 +16717,8 @@ var WorkspaceWatcher = class {
|
|
|
14707
16717
|
/** Typed kinds, so unlike a bare `fs.watch` rename there is nothing to stat
|
|
14708
16718
|
* to tell a create from a delete. */
|
|
14709
16719
|
onEvent(event) {
|
|
14710
|
-
if (!event.path.startsWith(this.
|
|
14711
|
-
const rel = event.path.slice(this.
|
|
16720
|
+
if (!event.path.startsWith(this.watchRoot)) return;
|
|
16721
|
+
const rel = event.path.slice(this.watchRoot.length).replace(/^\/+/, "");
|
|
14712
16722
|
if (!rel) return;
|
|
14713
16723
|
if (event.type === "create") this.record("added", rel);
|
|
14714
16724
|
else if (event.type === "delete") this.record("removed", rel);
|
|
@@ -14733,19 +16743,19 @@ var WorkspaceWatcher = class {
|
|
|
14733
16743
|
}
|
|
14734
16744
|
record(kind, relPath) {
|
|
14735
16745
|
if (this.closed) return;
|
|
14736
|
-
const
|
|
14737
|
-
const expiry = this.selfWrites.get(
|
|
16746
|
+
const path17 = "/" + relPath;
|
|
16747
|
+
const expiry = this.selfWrites.get(path17);
|
|
14738
16748
|
if (expiry !== void 0) {
|
|
14739
16749
|
if (expiry > Date.now()) return;
|
|
14740
|
-
this.selfWrites.delete(
|
|
16750
|
+
this.selfWrites.delete(path17);
|
|
14741
16751
|
}
|
|
14742
16752
|
const { added, changed, removed } = this.pending;
|
|
14743
|
-
added.delete(
|
|
14744
|
-
changed.delete(
|
|
14745
|
-
removed.delete(
|
|
14746
|
-
if (kind === "added") added.add(
|
|
14747
|
-
else if (kind === "changed") changed.add(
|
|
14748
|
-
else removed.add(
|
|
16753
|
+
added.delete(path17);
|
|
16754
|
+
changed.delete(path17);
|
|
16755
|
+
removed.delete(path17);
|
|
16756
|
+
if (kind === "added") added.add(path17);
|
|
16757
|
+
else if (kind === "changed") changed.add(path17);
|
|
16758
|
+
else removed.add(path17);
|
|
14749
16759
|
if (added.size + changed.size + removed.size > MAX_BATCH) {
|
|
14750
16760
|
this.pending = { ...emptyPending(), resync: true };
|
|
14751
16761
|
}
|
|
@@ -14764,8 +16774,8 @@ var WorkspaceWatcher = class {
|
|
|
14764
16774
|
this.pending = emptyPending();
|
|
14765
16775
|
if (!resync && !added.size && !changed.size && !removed.size) return;
|
|
14766
16776
|
const now = Date.now();
|
|
14767
|
-
for (const [
|
|
14768
|
-
if (expiry <= now) this.selfWrites.delete(
|
|
16777
|
+
for (const [path17, expiry] of this.selfWrites) {
|
|
16778
|
+
if (expiry <= now) this.selfWrites.delete(path17);
|
|
14769
16779
|
}
|
|
14770
16780
|
this.emit({
|
|
14771
16781
|
added: resync ? [] : [...added],
|
|
@@ -14850,16 +16860,15 @@ var TerminalModeEngine = class {
|
|
|
14850
16860
|
/** Suppress the change event our own write is about to produce, so a save
|
|
14851
16861
|
* never comes back to the editor as an outside edit. Normalized to the form
|
|
14852
16862
|
* the watcher emits, since callers spell the path either way. */
|
|
14853
|
-
expectWrite(postId,
|
|
14854
|
-
this.watchers.get(postId)?.expectWrite(posixNormalize(
|
|
16863
|
+
expectWrite(postId, path17) {
|
|
16864
|
+
this.watchers.get(postId)?.expectWrite(posixNormalize(path17));
|
|
14855
16865
|
}
|
|
14856
16866
|
// ── Lifecycle / ModeEngine ──
|
|
14857
16867
|
start(base, args) {
|
|
14858
16868
|
const session = {
|
|
14859
16869
|
...base,
|
|
14860
16870
|
openedFiles: [],
|
|
14861
|
-
buffers: {}
|
|
14862
|
-
exposed: []
|
|
16871
|
+
buffers: {}
|
|
14863
16872
|
};
|
|
14864
16873
|
this.sessions.set(session.postId, session);
|
|
14865
16874
|
this.emitter.emitSessionUpdate(session);
|
|
@@ -14923,8 +16932,32 @@ var TerminalModeEngine = class {
|
|
|
14923
16932
|
this.reservedNames.clear();
|
|
14924
16933
|
this.sessions.clear();
|
|
14925
16934
|
}
|
|
14926
|
-
|
|
14927
|
-
|
|
16935
|
+
/** Every tmux session this post owns, by the `terminal-<postId>-<tab>` name
|
|
16936
|
+
* tmuxName() builds. */
|
|
16937
|
+
async terminalSessionNames(target, postId) {
|
|
16938
|
+
const prefix = `terminal-${postId.replace(/[^a-zA-Z0-9._-]/g, "_")}-`;
|
|
16939
|
+
const stdout = await target.sh(
|
|
16940
|
+
`${tmuxCli(target)} list-sessions -F '#{session_name}' 2>/dev/null || true`
|
|
16941
|
+
).catch(() => "");
|
|
16942
|
+
return stdout.split("\n").map((line) => line.trim()).filter((name) => name.startsWith(prefix));
|
|
16943
|
+
}
|
|
16944
|
+
/** Kill the post's terminals and everything running inside them. Killing the
|
|
16945
|
+
* tmux session alone leaves a process that reparented away from the shell,
|
|
16946
|
+
* so each pane's process tree is killed first. */
|
|
16947
|
+
async killTerminals(postId) {
|
|
16948
|
+
const target = this.core.target();
|
|
16949
|
+
const names = await this.terminalSessionNames(target, postId);
|
|
16950
|
+
for (const name of names) {
|
|
16951
|
+
await target.sh(
|
|
16952
|
+
`for p in $(${tmuxCli(target)} list-panes -t ${name} -F '#{pane_pid}' 2>/dev/null); do pkill -TERM -P "$p" 2>/dev/null || true; done; ${tmuxCli(target)} kill-session -t ${name} 2>/dev/null || true`
|
|
16953
|
+
).catch(() => {
|
|
16954
|
+
});
|
|
16955
|
+
}
|
|
16956
|
+
}
|
|
16957
|
+
async teardown(session) {
|
|
16958
|
+
await this.killTerminals(session.postId).catch(() => {
|
|
16959
|
+
});
|
|
16960
|
+
await teardownSession(session, this.core.target());
|
|
14928
16961
|
}
|
|
14929
16962
|
putCard(s) {
|
|
14930
16963
|
return putSessionCard(s, this.core.target());
|
|
@@ -14961,8 +16994,7 @@ var TerminalModeEngine = class {
|
|
|
14961
16994
|
workspacePath: r.worktreePath,
|
|
14962
16995
|
agentSessionId: r.agentSessionId,
|
|
14963
16996
|
openedFiles: [],
|
|
14964
|
-
buffers: {}
|
|
14965
|
-
exposed: []
|
|
16997
|
+
buffers: {}
|
|
14966
16998
|
};
|
|
14967
16999
|
this.sessions.set(r.postId, session);
|
|
14968
17000
|
this.startWatching(session);
|
|
@@ -15004,8 +17036,7 @@ var TerminalModeEngine = class {
|
|
|
15004
17036
|
workspacePath: args.workspacePath,
|
|
15005
17037
|
agentSessionId: args.agentSessionId,
|
|
15006
17038
|
openedFiles: [],
|
|
15007
|
-
buffers: {}
|
|
15008
|
-
exposed: []
|
|
17039
|
+
buffers: {}
|
|
15009
17040
|
};
|
|
15010
17041
|
this.sessions.set(args.postId, session);
|
|
15011
17042
|
this.stopWatching(args.postId);
|
|
@@ -15025,13 +17056,18 @@ var TerminalModeEngine = class {
|
|
|
15025
17056
|
this.reservedNames.values()
|
|
15026
17057
|
);
|
|
15027
17058
|
this.reservedNames.set(session.postId, workName);
|
|
15028
|
-
const { path:
|
|
15029
|
-
|
|
17059
|
+
const { path: path17 } = await addWorktree(
|
|
17060
|
+
this.core.target(),
|
|
17061
|
+
workName,
|
|
17062
|
+
branch,
|
|
17063
|
+
"terminal"
|
|
17064
|
+
);
|
|
17065
|
+
session.workspacePath = path17;
|
|
15030
17066
|
await setGitIdentity(
|
|
15031
17067
|
this.core.target(),
|
|
15032
17068
|
config.gitName,
|
|
15033
17069
|
config.gitEmail,
|
|
15034
|
-
|
|
17070
|
+
path17
|
|
15035
17071
|
);
|
|
15036
17072
|
this.emitter.addLog(session, "system", "Worktree ready.");
|
|
15037
17073
|
} else {
|
|
@@ -15093,19 +17129,23 @@ var TerminalModeEngine = class {
|
|
|
15093
17129
|
}
|
|
15094
17130
|
async bareFsList({
|
|
15095
17131
|
postId,
|
|
15096
|
-
path:
|
|
17132
|
+
path: path17
|
|
15097
17133
|
}) {
|
|
15098
17134
|
const target = this.getBareTarget(postId);
|
|
15099
17135
|
const root = this.getBareRoot(postId);
|
|
15100
|
-
const abs = this.resolveBarePath(
|
|
17136
|
+
const abs = this.resolveBarePath(path17, root);
|
|
15101
17137
|
const relRoot = abs.slice(root.length) || "/";
|
|
15102
|
-
const out = await target.
|
|
17138
|
+
const out = await target.run([
|
|
17139
|
+
"sh",
|
|
17140
|
+
"-c",
|
|
17141
|
+
`cd ${shEscape(abs)} && for f in .* *; do
|
|
15103
17142
|
case "$f" in
|
|
15104
17143
|
"."|".."|".git"|"*") continue ;;
|
|
15105
17144
|
esac
|
|
15106
17145
|
[ -e "$f" ] || continue
|
|
15107
17146
|
if [ -d "$f" ]; then echo "d/$f"; else echo "f/$f"; fi
|
|
15108
|
-
done | LC_ALL=C sort`
|
|
17147
|
+
done | LC_ALL=C sort`
|
|
17148
|
+
]);
|
|
15109
17149
|
const entries = [];
|
|
15110
17150
|
for (const line of out.split("\n")) {
|
|
15111
17151
|
if (!line) continue;
|
|
@@ -15143,32 +17183,32 @@ var TerminalModeEngine = class {
|
|
|
15143
17183
|
}
|
|
15144
17184
|
async bareFsRead({
|
|
15145
17185
|
postId,
|
|
15146
|
-
path:
|
|
17186
|
+
path: path17
|
|
15147
17187
|
}) {
|
|
15148
17188
|
const target = this.getBareTarget(postId);
|
|
15149
|
-
const abs = this.resolveBarePath(
|
|
17189
|
+
const abs = this.resolveBarePath(path17, this.getBareRoot(postId));
|
|
15150
17190
|
const b64 = (await target.sh(`base64 < ${shEscape(abs)} | tr -d '
|
|
15151
17191
|
'`)).trim();
|
|
15152
17192
|
return { content: b64, encoding: "base64" };
|
|
15153
17193
|
}
|
|
15154
17194
|
async bareFsWrite({
|
|
15155
17195
|
postId,
|
|
15156
|
-
path:
|
|
17196
|
+
path: path17,
|
|
15157
17197
|
content,
|
|
15158
17198
|
encoding
|
|
15159
17199
|
}) {
|
|
15160
17200
|
const target = this.getBareTarget(postId);
|
|
15161
|
-
const abs = this.resolveBarePath(
|
|
17201
|
+
const abs = this.resolveBarePath(path17, this.getBareRoot(postId));
|
|
15162
17202
|
const b64 = encoding === "base64" ? content : Buffer.from(content, "utf-8").toString("base64");
|
|
15163
17203
|
if (!/^[A-Za-z0-9+/=\n\r]*$/.test(b64)) {
|
|
15164
17204
|
throw new Error("Invalid base64 payload.");
|
|
15165
17205
|
}
|
|
15166
|
-
this.expectWrite(String(postId),
|
|
17206
|
+
this.expectWrite(String(postId), path17);
|
|
15167
17207
|
await target.sh(
|
|
15168
17208
|
`mkdir -p ${shEscape(posixDirname(abs))} && printf %s ${shEscape(b64)} | base64 -d > ${shEscape(abs)}`
|
|
15169
17209
|
);
|
|
15170
17210
|
const session = this.sessions.get(String(postId));
|
|
15171
|
-
if (session) delete session.buffers[
|
|
17211
|
+
if (session) delete session.buffers[path17];
|
|
15172
17212
|
}
|
|
15173
17213
|
/** Write a dropped image to the per-space drops dir (`paths.root`, OUTSIDE the
|
|
15174
17214
|
* card worktree so it never touches the repo) and return its absolute path. */
|
|
@@ -15213,39 +17253,39 @@ var TerminalModeEngine = class {
|
|
|
15213
17253
|
}
|
|
15214
17254
|
bareOpenFile({
|
|
15215
17255
|
postId: rawPostId,
|
|
15216
|
-
path:
|
|
17256
|
+
path: path17
|
|
15217
17257
|
}) {
|
|
15218
17258
|
const session = this.sessions.get(String(rawPostId));
|
|
15219
17259
|
if (!session || session.mode !== "bare") return;
|
|
15220
|
-
if (!session.openedFiles.includes(
|
|
15221
|
-
session.openedFiles.push(
|
|
17260
|
+
if (!session.openedFiles.includes(path17)) {
|
|
17261
|
+
session.openedFiles.push(path17);
|
|
15222
17262
|
}
|
|
15223
17263
|
}
|
|
15224
17264
|
bareCloseFile({
|
|
15225
17265
|
postId: rawPostId,
|
|
15226
|
-
path:
|
|
17266
|
+
path: path17
|
|
15227
17267
|
}) {
|
|
15228
17268
|
const session = this.sessions.get(String(rawPostId));
|
|
15229
17269
|
if (!session || session.mode !== "bare") return;
|
|
15230
|
-
session.openedFiles = session.openedFiles.filter((p) => p !==
|
|
15231
|
-
delete session.buffers[
|
|
17270
|
+
session.openedFiles = session.openedFiles.filter((p) => p !== path17);
|
|
17271
|
+
delete session.buffers[path17];
|
|
15232
17272
|
}
|
|
15233
17273
|
bareBufferSet({
|
|
15234
17274
|
postId: rawPostId,
|
|
15235
|
-
path:
|
|
17275
|
+
path: path17,
|
|
15236
17276
|
content
|
|
15237
17277
|
}) {
|
|
15238
17278
|
const session = this.sessions.get(String(rawPostId));
|
|
15239
17279
|
if (!session || session.mode !== "bare") return;
|
|
15240
|
-
session.buffers[
|
|
17280
|
+
session.buffers[path17] = content;
|
|
15241
17281
|
}
|
|
15242
17282
|
async bareFsDelete({
|
|
15243
17283
|
postId,
|
|
15244
|
-
path:
|
|
17284
|
+
path: path17
|
|
15245
17285
|
}) {
|
|
15246
17286
|
const target = this.getBareTarget(postId);
|
|
15247
17287
|
const root = this.getBareRoot(postId);
|
|
15248
|
-
const abs = this.resolveBarePath(
|
|
17288
|
+
const abs = this.resolveBarePath(path17, root);
|
|
15249
17289
|
if (abs === root) {
|
|
15250
17290
|
throw new Error("Refusing to delete the repo root.");
|
|
15251
17291
|
}
|
|
@@ -15253,10 +17293,10 @@ var TerminalModeEngine = class {
|
|
|
15253
17293
|
}
|
|
15254
17294
|
async bareFsMkdir({
|
|
15255
17295
|
postId,
|
|
15256
|
-
path:
|
|
17296
|
+
path: path17
|
|
15257
17297
|
}) {
|
|
15258
17298
|
const target = this.getBareTarget(postId);
|
|
15259
|
-
const abs = this.resolveBarePath(
|
|
17299
|
+
const abs = this.resolveBarePath(path17, this.getBareRoot(postId));
|
|
15260
17300
|
await target.sh(`mkdir -p ${shEscape(abs)}`);
|
|
15261
17301
|
}
|
|
15262
17302
|
async bareReview({
|
|
@@ -15317,25 +17357,56 @@ var TerminalModeEngine = class {
|
|
|
15317
17357
|
commitMessage: session.commitMessage
|
|
15318
17358
|
};
|
|
15319
17359
|
}
|
|
15320
|
-
|
|
15321
|
-
|
|
15322
|
-
|
|
15323
|
-
|
|
15324
|
-
|
|
17360
|
+
/** The worktree a card's git panel acts on, or '' when the card has none (a
|
|
17361
|
+
* OneDrive-only space has no tree to diff). Throws for a session that isn't
|
|
17362
|
+
* live, so a stale modal gets an error rather than acting on whatever
|
|
17363
|
+
* happens to sit at the space root. */
|
|
17364
|
+
gitWorkdir(rawPostId) {
|
|
17365
|
+
const session = this.sessions.get(String(rawPostId));
|
|
15325
17366
|
if (!session) throw new Error("No active session for this card.");
|
|
15326
17367
|
if (session.mode !== "bare") {
|
|
15327
|
-
throw new Error("
|
|
17368
|
+
throw new Error("Git actions are only valid in Bare mode.");
|
|
15328
17369
|
}
|
|
15329
17370
|
if (session.phase !== "bare-active" && session.phase !== "bare-review") {
|
|
15330
17371
|
throw new Error("Workspace is not ready.");
|
|
15331
17372
|
}
|
|
15332
|
-
|
|
15333
|
-
|
|
15334
|
-
|
|
15335
|
-
|
|
15336
|
-
|
|
15337
|
-
|
|
15338
|
-
);
|
|
17373
|
+
return session.workspacePath || "";
|
|
17374
|
+
}
|
|
17375
|
+
async bareGitStatus({
|
|
17376
|
+
postId
|
|
17377
|
+
}) {
|
|
17378
|
+
const workdir = this.gitWorkdir(postId);
|
|
17379
|
+
if (!workdir) return { diff: "", changedFiles: [] };
|
|
17380
|
+
return await readGitPanel(this.core.target(), workdir);
|
|
17381
|
+
}
|
|
17382
|
+
/** Both staging calls answer with the panel's next state: the index is
|
|
17383
|
+
* inside .git, which the working-tree watcher prunes, so nothing else would
|
|
17384
|
+
* tell the panel its rows just moved. */
|
|
17385
|
+
async bareGitStage({
|
|
17386
|
+
postId: rawPostId,
|
|
17387
|
+
paths
|
|
17388
|
+
}) {
|
|
17389
|
+
const workdir = this.gitWorkdir(rawPostId);
|
|
17390
|
+
if (!workdir) return { diff: "", changedFiles: [] };
|
|
17391
|
+
await stagePaths(this.core.target(), workdir, paths);
|
|
17392
|
+
return await readGitPanel(this.core.target(), workdir);
|
|
17393
|
+
}
|
|
17394
|
+
async bareGitUnstage({
|
|
17395
|
+
postId: rawPostId,
|
|
17396
|
+
paths
|
|
17397
|
+
}) {
|
|
17398
|
+
const workdir = this.gitWorkdir(rawPostId);
|
|
17399
|
+
if (!workdir) return { diff: "", changedFiles: [] };
|
|
17400
|
+
await unstagePaths(this.core.target(), workdir, paths);
|
|
17401
|
+
return await readGitPanel(this.core.target(), workdir);
|
|
17402
|
+
}
|
|
17403
|
+
async bareGitCommitDiff({
|
|
17404
|
+
postId,
|
|
17405
|
+
sha
|
|
17406
|
+
}) {
|
|
17407
|
+
const workdir = this.gitWorkdir(postId);
|
|
17408
|
+
if (!workdir) return { diff: "", changedFiles: [] };
|
|
17409
|
+
return await readCommitDiff(this.core.target(), workdir, sha);
|
|
15339
17410
|
}
|
|
15340
17411
|
/** `targetOverride` carries the cross-mode lookup, so tabs resolve for cards
|
|
15341
17412
|
* this engine doesn't own - the crashed claude sessions users most need. */
|
|
@@ -15368,6 +17439,87 @@ var TerminalModeEngine = class {
|
|
|
15368
17439
|
return [];
|
|
15369
17440
|
}
|
|
15370
17441
|
}
|
|
17442
|
+
/** Pane pids for this card's tabs, keyed by tab id. The pane's own shell is
|
|
17443
|
+
* the root of everything the user started in that tab. */
|
|
17444
|
+
async panePids(target, postId) {
|
|
17445
|
+
const prefix = `terminal-${postId.replace(/[^a-zA-Z0-9._-]/g, "_")}-`;
|
|
17446
|
+
const stdout = await target.sh(
|
|
17447
|
+
`${tmuxCli(target)} list-panes -a -F '#{session_name}|#{pane_pid}' 2>/dev/null || true`
|
|
17448
|
+
).catch(() => "");
|
|
17449
|
+
const panes = [];
|
|
17450
|
+
for (const raw of stdout.split("\n")) {
|
|
17451
|
+
const line = raw.trim();
|
|
17452
|
+
if (!line.startsWith(prefix)) continue;
|
|
17453
|
+
const rest = line.slice(prefix.length);
|
|
17454
|
+
const sep = rest.indexOf("|");
|
|
17455
|
+
if (sep === -1) continue;
|
|
17456
|
+
const pid = Number(rest.slice(sep + 1));
|
|
17457
|
+
if (!Number.isInteger(pid) || pid <= 0) continue;
|
|
17458
|
+
panes.push({ tabId: rest.slice(0, sep), pid });
|
|
17459
|
+
}
|
|
17460
|
+
return panes;
|
|
17461
|
+
}
|
|
17462
|
+
async bareListProcesses({
|
|
17463
|
+
postId: rawPostId
|
|
17464
|
+
}) {
|
|
17465
|
+
const postId = String(rawPostId);
|
|
17466
|
+
const target = this.getBareTarget(postId);
|
|
17467
|
+
const panes = await this.panePids(target, postId);
|
|
17468
|
+
if (panes.length === 0) return [];
|
|
17469
|
+
const table = parsePsTable(
|
|
17470
|
+
await target.run(["ps", "-axo", PS_FORMAT]).catch(() => "")
|
|
17471
|
+
);
|
|
17472
|
+
const found = [];
|
|
17473
|
+
for (const pane of panes) {
|
|
17474
|
+
for (const row of processesForPane(table, pane.pid)) {
|
|
17475
|
+
found.push({
|
|
17476
|
+
pid: row.pid,
|
|
17477
|
+
tabId: pane.tabId,
|
|
17478
|
+
command: commandOf(row.args),
|
|
17479
|
+
args: row.args,
|
|
17480
|
+
cpu: row.cpu,
|
|
17481
|
+
memoryKb: row.rssKb,
|
|
17482
|
+
elapsed: row.elapsed,
|
|
17483
|
+
ports: [],
|
|
17484
|
+
connections: 0
|
|
17485
|
+
});
|
|
17486
|
+
}
|
|
17487
|
+
}
|
|
17488
|
+
if (found.length === 0) return [];
|
|
17489
|
+
const sockets = await socketsFor(
|
|
17490
|
+
target,
|
|
17491
|
+
found.map((p) => p.pid)
|
|
17492
|
+
);
|
|
17493
|
+
for (const proc of found) {
|
|
17494
|
+
const use = sockets.get(proc.pid);
|
|
17495
|
+
proc.ports = use?.ports ?? [];
|
|
17496
|
+
proc.connections = use?.connections ?? 0;
|
|
17497
|
+
}
|
|
17498
|
+
return found.sort(
|
|
17499
|
+
(a, b) => (Number(a.tabId) || 0) - (Number(b.tabId) || 0) || a.pid - b.pid
|
|
17500
|
+
);
|
|
17501
|
+
}
|
|
17502
|
+
async bareKillProcess({
|
|
17503
|
+
postId: rawPostId,
|
|
17504
|
+
pid
|
|
17505
|
+
}) {
|
|
17506
|
+
const postId = String(rawPostId);
|
|
17507
|
+
const target = this.getBareTarget(postId);
|
|
17508
|
+
if (!Number.isInteger(pid) || pid <= 1) {
|
|
17509
|
+
throw new Error("Not a process this card can stop.");
|
|
17510
|
+
}
|
|
17511
|
+
const panes = await this.panePids(target, postId);
|
|
17512
|
+
const table = parsePsTable(
|
|
17513
|
+
await target.run(["ps", "-axo", PS_FORMAT]).catch(() => "")
|
|
17514
|
+
);
|
|
17515
|
+
const mine = panes.some(
|
|
17516
|
+
(pane) => processesForPane(table, pane.pid).some((row) => row.pid === pid)
|
|
17517
|
+
);
|
|
17518
|
+
if (!mine) {
|
|
17519
|
+
throw new Error("That process is not running in this card.");
|
|
17520
|
+
}
|
|
17521
|
+
await target.run(["kill", "-TERM", String(pid)]);
|
|
17522
|
+
}
|
|
15371
17523
|
// ── port forwarding (factiii-expose / factiii-unexpose) ──
|
|
15372
17524
|
async bareExpose({
|
|
15373
17525
|
postId,
|
|
@@ -15375,7 +17527,6 @@ var TerminalModeEngine = class {
|
|
|
15375
17527
|
name
|
|
15376
17528
|
}) {
|
|
15377
17529
|
const target = this.getBareTarget(postId);
|
|
15378
|
-
const session = this.sessions.get(String(postId));
|
|
15379
17530
|
const safeName = name.trim();
|
|
15380
17531
|
if (!/^[a-zA-Z0-9-]+$/.test(safeName)) {
|
|
15381
17532
|
throw new Error("Name must be letters, numbers, or dashes.");
|
|
@@ -15388,30 +17539,28 @@ var TerminalModeEngine = class {
|
|
|
15388
17539
|
if (!/^https?:\/\//.test(url2)) {
|
|
15389
17540
|
throw new Error(out || "Failed to expose port.");
|
|
15390
17541
|
}
|
|
15391
|
-
|
|
15392
|
-
session.exposed = session.exposed.filter((e) => e.name !== safeName);
|
|
15393
|
-
session.exposed.push(entry);
|
|
15394
|
-
return entry;
|
|
17542
|
+
return { name: safeName, port, url: url2 };
|
|
15395
17543
|
}
|
|
15396
17544
|
async bareUnexpose({
|
|
15397
17545
|
postId,
|
|
15398
17546
|
name
|
|
15399
17547
|
}) {
|
|
15400
17548
|
const target = this.getBareTarget(postId);
|
|
15401
|
-
const session = this.sessions.get(String(postId));
|
|
15402
17549
|
const safeName = name.trim();
|
|
15403
17550
|
if (!/^[a-zA-Z0-9-]+$/.test(safeName)) {
|
|
15404
17551
|
throw new Error("Invalid tunnel name.");
|
|
15405
17552
|
}
|
|
15406
17553
|
await target.sh(`factiii-unexpose ${shEscape(safeName)} 2>&1 || true`);
|
|
15407
|
-
session.exposed = session.exposed.filter((e) => e.name !== safeName);
|
|
15408
17554
|
}
|
|
15409
|
-
|
|
17555
|
+
/** Derived, never remembered: `factiii-expose` is on the agent's PATH, so the
|
|
17556
|
+
* terminal and the agent both open tunnels without telling us, and a runner
|
|
17557
|
+
* restart would forget any list we kept. */
|
|
17558
|
+
async bareListExposed({
|
|
15410
17559
|
postId
|
|
15411
17560
|
}) {
|
|
15412
17561
|
const session = this.sessions.get(String(postId));
|
|
15413
17562
|
if (!session || session.mode !== "bare") return [];
|
|
15414
|
-
return
|
|
17563
|
+
return await listLiveTunnels(this.core.target());
|
|
15415
17564
|
}
|
|
15416
17565
|
};
|
|
15417
17566
|
|
|
@@ -15689,571 +17838,204 @@ var CodexLoginFlow = class {
|
|
|
15689
17838
|
reject(new Error("Codex sign-in cancelled."));
|
|
15690
17839
|
}
|
|
15691
17840
|
await this.cleanup();
|
|
15692
|
-
}
|
|
15693
|
-
onData(chunk) {
|
|
15694
|
-
this.output += this.strip(chunk.toString("utf-8"));
|
|
15695
|
-
if (this.output.length > 2e5) {
|
|
15696
|
-
this.output = this.output.slice(-1e5);
|
|
15697
|
-
}
|
|
15698
|
-
if (this.startResolver) {
|
|
15699
|
-
const url2 = this.output.match(/https:\/\/[^\s\x00-\x1f]+/)?.[0];
|
|
15700
|
-
const code = this.output.match(CODE_LINE_RE)?.[1] ?? this.output.match(CODE_TOKEN_RE)?.[1];
|
|
15701
|
-
if (url2 && code) {
|
|
15702
|
-
const resolve2 = this.startResolver;
|
|
15703
|
-
this.startResolver = null;
|
|
15704
|
-
this.startRejecter = null;
|
|
15705
|
-
resolve2({ url: url2.replace(/[.,;)\]]+$/, ""), code });
|
|
15706
|
-
}
|
|
15707
|
-
return;
|
|
15708
|
-
}
|
|
15709
|
-
if (this.settled) return;
|
|
15710
|
-
if (/success|logged in/i.test(this.output)) {
|
|
15711
|
-
this.settled = true;
|
|
15712
|
-
void this.verifyAndComplete("");
|
|
15713
|
-
}
|
|
15714
|
-
}
|
|
15715
|
-
// Process ended (or success was printed): the auth.json on the cache volume
|
|
15716
|
-
// is the ground truth for whether the sign-in worked.
|
|
15717
|
-
async verifyAndComplete(failTail) {
|
|
15718
|
-
const ok = await loginCredentialsExist("codex", this.target);
|
|
15719
|
-
await this.cleanup();
|
|
15720
|
-
this.onDone(
|
|
15721
|
-
ok ? { status: "complete" } : {
|
|
15722
|
-
status: "error",
|
|
15723
|
-
message: failTail || "Codex sign-in ended without signing in."
|
|
15724
|
-
}
|
|
15725
|
-
);
|
|
15726
|
-
}
|
|
15727
|
-
async onClose() {
|
|
15728
|
-
if (this.settled) return;
|
|
15729
|
-
const tail2 = this.output.trim().slice(-300);
|
|
15730
|
-
if (this.startRejecter) {
|
|
15731
|
-
this.fail(tail2 || "Codex sign-in ended unexpectedly.");
|
|
15732
|
-
return;
|
|
15733
|
-
}
|
|
15734
|
-
this.settled = true;
|
|
15735
|
-
await this.verifyAndComplete(tail2);
|
|
15736
|
-
}
|
|
15737
|
-
fail(message) {
|
|
15738
|
-
if (this.settled) return;
|
|
15739
|
-
this.settled = true;
|
|
15740
|
-
if (this.startRejecter) {
|
|
15741
|
-
const reject = this.startRejecter;
|
|
15742
|
-
this.startResolver = null;
|
|
15743
|
-
this.startRejecter = null;
|
|
15744
|
-
reject(new Error(message));
|
|
15745
|
-
void this.cleanup();
|
|
15746
|
-
return;
|
|
15747
|
-
}
|
|
15748
|
-
void this.cleanup();
|
|
15749
|
-
this.onDone({ status: "error", message });
|
|
15750
|
-
}
|
|
15751
|
-
async cleanup() {
|
|
15752
|
-
await stopLoginExec(this.target, "codex", this.spaceSlug);
|
|
15753
|
-
}
|
|
15754
|
-
reset() {
|
|
15755
|
-
this.output = "";
|
|
15756
|
-
this.strip = createAnsiStripper();
|
|
15757
|
-
this.settled = false;
|
|
15758
|
-
this.startResolver = null;
|
|
15759
|
-
this.startRejecter = null;
|
|
15760
|
-
}
|
|
15761
|
-
};
|
|
15762
|
-
|
|
15763
|
-
// ../../shared/all/helpers/board-agent-core/github-auth.ts
|
|
15764
|
-
var DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
15765
|
-
var TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
15766
|
-
var API_BASE = "https://api.github.com";
|
|
15767
|
-
var GITHUB_SCOPES = "repo read:user user:email";
|
|
15768
|
-
var DEVICE_CODE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
|
|
15769
|
-
async function startGithubDeviceCode(clientId) {
|
|
15770
|
-
const res = await fetch(DEVICE_CODE_URL, {
|
|
15771
|
-
method: "POST",
|
|
15772
|
-
headers: {
|
|
15773
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
15774
|
-
Accept: "application/json"
|
|
15775
|
-
},
|
|
15776
|
-
body: new URLSearchParams({ client_id: clientId, scope: GITHUB_SCOPES })
|
|
15777
|
-
});
|
|
15778
|
-
if (!res.ok) {
|
|
15779
|
-
throw new Error(`GitHub device-code start failed: ${await res.text()}`);
|
|
15780
|
-
}
|
|
15781
|
-
const data = await res.json();
|
|
15782
|
-
if (data.error || !data.device_code || !data.user_code) {
|
|
15783
|
-
throw new Error(
|
|
15784
|
-
data.error_description || data.error || "GitHub returned no device code. Is device flow enabled on the OAuth App?"
|
|
15785
|
-
);
|
|
15786
|
-
}
|
|
15787
|
-
return {
|
|
15788
|
-
deviceCode: data.device_code,
|
|
15789
|
-
userCode: data.user_code,
|
|
15790
|
-
verificationUri: data.verification_uri || "https://github.com/login/device",
|
|
15791
|
-
expiresIn: data.expires_in ?? 900,
|
|
15792
|
-
interval: data.interval ?? 5
|
|
15793
|
-
};
|
|
15794
|
-
}
|
|
15795
|
-
async function pollGithubDeviceCode(clientId, deviceCode) {
|
|
15796
|
-
const res = await fetch(TOKEN_URL, {
|
|
15797
|
-
method: "POST",
|
|
15798
|
-
headers: {
|
|
15799
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
15800
|
-
Accept: "application/json"
|
|
15801
|
-
},
|
|
15802
|
-
body: new URLSearchParams({
|
|
15803
|
-
client_id: clientId,
|
|
15804
|
-
device_code: deviceCode,
|
|
15805
|
-
grant_type: DEVICE_CODE_GRANT
|
|
15806
|
-
})
|
|
15807
|
-
});
|
|
15808
|
-
const data = await res.json();
|
|
15809
|
-
if (data.access_token) {
|
|
15810
|
-
return { status: "complete", accessToken: data.access_token };
|
|
15811
|
-
}
|
|
15812
|
-
switch (data.error) {
|
|
15813
|
-
case "authorization_pending":
|
|
15814
|
-
return { status: "pending" };
|
|
15815
|
-
case "slow_down":
|
|
15816
|
-
return { status: "slow_down" };
|
|
15817
|
-
case "expired_token":
|
|
15818
|
-
return { status: "expired" };
|
|
15819
|
-
case "access_denied":
|
|
15820
|
-
return { status: "declined" };
|
|
15821
|
-
default:
|
|
15822
|
-
return {
|
|
15823
|
-
status: "error",
|
|
15824
|
-
message: data.error_description || data.error || "GitHub sign-in failed."
|
|
15825
|
-
};
|
|
15826
|
-
}
|
|
15827
|
-
}
|
|
15828
|
-
async function githubApi(token, path16) {
|
|
15829
|
-
const res = await fetch(`${API_BASE}${path16}`, {
|
|
15830
|
-
headers: {
|
|
15831
|
-
Authorization: `Bearer ${token}`,
|
|
15832
|
-
Accept: "application/vnd.github+json",
|
|
15833
|
-
"X-GitHub-Api-Version": "2022-11-28"
|
|
15834
|
-
}
|
|
15835
|
-
});
|
|
15836
|
-
if (!res.ok) {
|
|
15837
|
-
throw new Error(
|
|
15838
|
-
`GitHub ${path16} failed (${res.status}): ${await res.text()}`
|
|
15839
|
-
);
|
|
15840
|
-
}
|
|
15841
|
-
return await res.json();
|
|
15842
|
-
}
|
|
15843
|
-
async function fetchGithubIdentity(token) {
|
|
15844
|
-
const user = await githubApi(token, "/user");
|
|
15845
|
-
let email = user.email ?? "";
|
|
15846
|
-
if (!email) {
|
|
15847
|
-
try {
|
|
15848
|
-
const emails = await githubApi(token, "/user/emails");
|
|
15849
|
-
email = emails.find((e) => e.primary && e.verified)?.email ?? emails.find((e) => e.verified)?.email ?? "";
|
|
15850
|
-
} catch {
|
|
15851
|
-
}
|
|
15852
|
-
}
|
|
15853
|
-
if (!email) email = `${user.id}+${user.login}@users.noreply.github.com`;
|
|
15854
|
-
return { login: user.login, name: user.name || user.login, email };
|
|
15855
|
-
}
|
|
15856
|
-
async function listGithubRepos(token) {
|
|
15857
|
-
const out = [];
|
|
15858
|
-
for (let page = 1; page <= 3; page++) {
|
|
15859
|
-
const batch = await githubApi(token, `/user/repos?per_page=100&sort=pushed&page=${page}`);
|
|
15860
|
-
for (const r of batch) {
|
|
15861
|
-
if (r.permissions && !r.permissions.push) continue;
|
|
15862
|
-
out.push({
|
|
15863
|
-
fullName: r.full_name,
|
|
15864
|
-
// Strip any embedded credentials; buildCloneUrl adds the token.
|
|
15865
|
-
cloneUrl: r.clone_url.replace(/\/\/[^@/]*@/, "//"),
|
|
15866
|
-
defaultBranch: r.default_branch,
|
|
15867
|
-
private: r.private
|
|
15868
|
-
});
|
|
15869
|
-
}
|
|
15870
|
-
if (batch.length < 100) break;
|
|
15871
|
-
}
|
|
15872
|
-
return out;
|
|
15873
|
-
}
|
|
15874
|
-
async function listGithubBranches(token, fullName) {
|
|
15875
|
-
const branches = await githubApi(
|
|
15876
|
-
token,
|
|
15877
|
-
`/repos/${fullName}/branches?per_page=100`
|
|
15878
|
-
);
|
|
15879
|
-
return branches.map((b) => b.name);
|
|
15880
|
-
}
|
|
15881
|
-
|
|
15882
|
-
// ../../shared/all/helpers/board-agent-core/host-deploy.ts
|
|
15883
|
-
var import_child_process3 = require("child_process");
|
|
15884
|
-
var import_promises3 = __toESM(require("fs/promises"));
|
|
15885
|
-
var import_path5 = __toESM(require("path"));
|
|
15886
|
-
var import_util5 = require("util");
|
|
15887
|
-
var execFileAsync2 = (0, import_util5.promisify)(import_child_process3.execFile);
|
|
15888
|
-
var SKILL_DIR = ".agents/skills/deploy";
|
|
15889
|
-
var SKILL_PATH = `${SKILL_DIR}/SKILL.md`;
|
|
15890
|
-
var VARIABLES_PATH = `${SKILL_DIR}/required-variables.json`;
|
|
15891
|
-
var CHECKER_PATH = `${SKILL_DIR}/check-environment.sh`;
|
|
15892
|
-
var CLAUDE_SKILLS_DIR = ".claude/skills";
|
|
15893
|
-
var CLAUDE_LINK = `${CLAUDE_SKILLS_DIR}/deploy`;
|
|
15894
|
-
async function ensureClaudeSkillLink(workspace) {
|
|
15895
|
-
const linkPath = import_path5.default.join(workspace, CLAUDE_LINK);
|
|
15896
|
-
try {
|
|
15897
|
-
await import_promises3.default.lstat(linkPath);
|
|
15898
|
-
return;
|
|
15899
|
-
} catch {
|
|
15900
|
-
}
|
|
15901
|
-
await import_promises3.default.mkdir(import_path5.default.join(workspace, CLAUDE_SKILLS_DIR), { recursive: true });
|
|
15902
|
-
await import_promises3.default.symlink("../../.agents/skills/deploy", linkPath, "dir");
|
|
15903
|
-
}
|
|
15904
|
-
var GIT_NO_HELPER = ["-c", "credential.helper="];
|
|
15905
|
-
var GIT_ENV = { ...process.env, GIT_TERMINAL_PROMPT: "0" };
|
|
15906
|
-
async function hostGit(cwd, ...args) {
|
|
15907
|
-
const { stdout } = await execFileAsync2("git", [...GIT_NO_HELPER, ...args], {
|
|
15908
|
-
cwd,
|
|
15909
|
-
env: GIT_ENV,
|
|
15910
|
-
maxBuffer: 16 * 1024 * 1024
|
|
15911
|
-
});
|
|
15912
|
-
return stdout;
|
|
15913
|
-
}
|
|
15914
|
-
async function checkDeployStatus(opts) {
|
|
15915
|
-
const workspace = import_path5.default.join(opts.spaceDir, "workspace");
|
|
15916
|
-
const branch = (opts.mainBranch || "main").replace(/[^\w./-]/g, "");
|
|
15917
|
-
const url2 = buildCloneUrl(opts.repoUrl, opts.githubToken);
|
|
15918
|
-
let cloned = false;
|
|
15919
|
-
try {
|
|
15920
|
-
await hostGit(workspace, "rev-parse", "--git-dir");
|
|
15921
|
-
await hostGit(workspace, "remote", "set-url", "origin", url2);
|
|
15922
|
-
} catch {
|
|
15923
|
-
await execFileAsync2(
|
|
15924
|
-
"git",
|
|
15925
|
-
[...GIT_NO_HELPER, "clone", "--branch", branch, url2, workspace],
|
|
15926
|
-
{ env: GIT_ENV }
|
|
15927
|
-
);
|
|
15928
|
-
cloned = true;
|
|
15929
|
-
}
|
|
15930
|
-
if (!cloned) await hostGit(workspace, "fetch", "origin", branch);
|
|
15931
|
-
const has = async (refPath) => {
|
|
15932
|
-
try {
|
|
15933
|
-
await hostGit(workspace, "cat-file", "-e", refPath);
|
|
15934
|
-
return true;
|
|
15935
|
-
} catch {
|
|
15936
|
-
return false;
|
|
17841
|
+
}
|
|
17842
|
+
onData(chunk) {
|
|
17843
|
+
this.output += this.strip(chunk.toString("utf-8"));
|
|
17844
|
+
if (this.output.length > 2e5) {
|
|
17845
|
+
this.output = this.output.slice(-1e5);
|
|
15937
17846
|
}
|
|
15938
|
-
|
|
15939
|
-
|
|
15940
|
-
|
|
15941
|
-
|
|
15942
|
-
|
|
15943
|
-
|
|
15944
|
-
|
|
15945
|
-
"
|
|
15946
|
-
"origin",
|
|
15947
|
-
"+refs/heads/deploy-skill*:refs/remotes/origin/deploy-skill*"
|
|
15948
|
-
);
|
|
15949
|
-
const refs = await hostGit(
|
|
15950
|
-
workspace,
|
|
15951
|
-
"for-each-ref",
|
|
15952
|
-
"--sort=-committerdate",
|
|
15953
|
-
"--format=%(refname:short)",
|
|
15954
|
-
"refs/remotes/origin/deploy-skill*"
|
|
15955
|
-
);
|
|
15956
|
-
for (const ref of refs.split("\n").filter(Boolean)) {
|
|
15957
|
-
if (await hasAll(ref)) {
|
|
15958
|
-
pendingBranch = ref.replace(/^origin\//, "");
|
|
15959
|
-
break;
|
|
15960
|
-
}
|
|
17847
|
+
if (this.startResolver) {
|
|
17848
|
+
const url2 = this.output.match(/https:\/\/[^\s\x00-\x1f]+/)?.[0];
|
|
17849
|
+
const code = this.output.match(CODE_LINE_RE)?.[1] ?? this.output.match(CODE_TOKEN_RE)?.[1];
|
|
17850
|
+
if (url2 && code) {
|
|
17851
|
+
const resolve2 = this.startResolver;
|
|
17852
|
+
this.startResolver = null;
|
|
17853
|
+
this.startRejecter = null;
|
|
17854
|
+
resolve2({ url: url2.replace(/[.,;)\]]+$/, ""), code });
|
|
15961
17855
|
}
|
|
15962
|
-
|
|
17856
|
+
return;
|
|
15963
17857
|
}
|
|
15964
|
-
|
|
15965
|
-
|
|
15966
|
-
|
|
15967
|
-
|
|
15968
|
-
try {
|
|
15969
|
-
const raw = await hostGit(
|
|
15970
|
-
workspace,
|
|
15971
|
-
"cat-file",
|
|
15972
|
-
"-p",
|
|
15973
|
-
`origin/${branch}:${VARIABLES_PATH}`
|
|
15974
|
-
);
|
|
15975
|
-
variables = external_exports.array(requiredVariableSchema).parse(JSON.parse(raw));
|
|
15976
|
-
} catch {
|
|
17858
|
+
if (this.settled) return;
|
|
17859
|
+
if (/success|logged in/i.test(this.output)) {
|
|
17860
|
+
this.settled = true;
|
|
17861
|
+
void this.verifyAndComplete("");
|
|
15977
17862
|
}
|
|
15978
17863
|
}
|
|
15979
|
-
|
|
15980
|
-
|
|
15981
|
-
|
|
15982
|
-
|
|
15983
|
-
|
|
15984
|
-
|
|
15985
|
-
|
|
15986
|
-
|
|
15987
|
-
|
|
15988
|
-
|
|
15989
|
-
key: external_exports.string().min(1),
|
|
15990
|
-
title: external_exports.string().catch(""),
|
|
15991
|
-
description: external_exports.string().catch(""),
|
|
15992
|
-
category: external_exports.string().catch("general"),
|
|
15993
|
-
sensitive: external_exports.boolean().catch(true)
|
|
15994
|
-
});
|
|
15995
|
-
function spaceEnv(spaceDir) {
|
|
15996
|
-
const paths = hostSpacePaths(spaceDir);
|
|
15997
|
-
return {
|
|
15998
|
-
WORKSPACE: paths.workspace,
|
|
15999
|
-
BACKUPS_DIR: paths.backups,
|
|
16000
|
-
BUILDS_DIR: paths.builds,
|
|
16001
|
-
// No keychain credential helper and no interactive git prompts anywhere
|
|
16002
|
-
// in the deploy surface (agent sessions + authorized commands): auth
|
|
16003
|
-
// comes from the secrets store or the tokened URL, never the keychain.
|
|
16004
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
16005
|
-
GIT_CONFIG_COUNT: "1",
|
|
16006
|
-
GIT_CONFIG_KEY_0: "credential.helper",
|
|
16007
|
-
GIT_CONFIG_VALUE_0: ""
|
|
16008
|
-
};
|
|
16009
|
-
}
|
|
16010
|
-
async function readContract(workspace) {
|
|
16011
|
-
const skill = await import_promises3.default.readFile(import_path5.default.join(workspace, SKILL_PATH), "utf-8").catch(() => {
|
|
16012
|
-
throw new Error("The creator wrote no SKILL.md.");
|
|
16013
|
-
});
|
|
16014
|
-
const checker = await import_promises3.default.readFile(import_path5.default.join(workspace, CHECKER_PATH), "utf-8").catch(() => {
|
|
16015
|
-
throw new Error("The creator wrote no check-environment.sh.");
|
|
16016
|
-
});
|
|
16017
|
-
try {
|
|
16018
|
-
const raw = JSON.parse(
|
|
16019
|
-
await import_promises3.default.readFile(import_path5.default.join(workspace, VARIABLES_PATH), "utf-8")
|
|
17864
|
+
// Process ended (or success was printed): the auth.json on the cache volume
|
|
17865
|
+
// is the ground truth for whether the sign-in worked.
|
|
17866
|
+
async verifyAndComplete(failTail) {
|
|
17867
|
+
const ok = await loginCredentialsExist("codex", this.target);
|
|
17868
|
+
await this.cleanup();
|
|
17869
|
+
this.onDone(
|
|
17870
|
+
ok ? { status: "complete" } : {
|
|
17871
|
+
status: "error",
|
|
17872
|
+
message: failTail || "Codex sign-in ended without signing in."
|
|
17873
|
+
}
|
|
16020
17874
|
);
|
|
16021
|
-
return {
|
|
16022
|
-
skill,
|
|
16023
|
-
checker,
|
|
16024
|
-
variables: external_exports.array(requiredVariableSchema).parse(raw)
|
|
16025
|
-
};
|
|
16026
|
-
} catch {
|
|
16027
|
-
throw new Error("The creator wrote no valid required-variables.json.");
|
|
16028
|
-
}
|
|
16029
|
-
}
|
|
16030
|
-
async function generateDeploySkill(opts) {
|
|
16031
|
-
const { instructions, sendLog } = opts;
|
|
16032
|
-
if (!instructions.trim()) {
|
|
16033
|
-
throw new Error("Describe your deployment process first.");
|
|
16034
17875
|
}
|
|
16035
|
-
|
|
16036
|
-
|
|
16037
|
-
|
|
16038
|
-
|
|
16039
|
-
|
|
16040
|
-
|
|
16041
|
-
await hostGit(
|
|
16042
|
-
workspace,
|
|
16043
|
-
"checkout",
|
|
16044
|
-
"-B",
|
|
16045
|
-
status.branch,
|
|
16046
|
-
`origin/${status.branch}`
|
|
16047
|
-
);
|
|
16048
|
-
await hostGit(workspace, "clean", "-fd");
|
|
16049
|
-
sendLog({
|
|
16050
|
-
type: "system",
|
|
16051
|
-
content: "Analyzing the repo and writing the deploy skill..."
|
|
16052
|
-
});
|
|
16053
|
-
const { result } = await spawnAgent(
|
|
16054
|
-
opts.provider,
|
|
16055
|
-
hostTarget(opts.spaceDir),
|
|
16056
|
-
{
|
|
16057
|
-
cwd: workspace,
|
|
16058
|
-
prompt: buildPrompt(prompts_default.deployCreate, {
|
|
16059
|
-
INSTRUCTIONS: instructions
|
|
16060
|
-
}),
|
|
16061
|
-
allowedTools: "Read,Grep,Glob,Bash,Edit,Write,WebSearch,WebFetch",
|
|
16062
|
-
// auto: the per-action classifier blocks escalations/unrecognized-infra
|
|
16063
|
-
// actions - a host session with no human reviewing each command should
|
|
16064
|
-
// not run bypassPermissions. (Codex maps this to a workspace-write
|
|
16065
|
-
// sandbox; see agent.ts.)
|
|
16066
|
-
permissionMode: "auto",
|
|
16067
|
-
model: "claude-opus-4-6",
|
|
16068
|
-
includePartialMessages: true,
|
|
16069
|
-
env: spaceEnv(opts.spaceDir),
|
|
16070
|
-
callbacks: { onLog: (entry) => sendLog(entry) }
|
|
16071
|
-
}
|
|
16072
|
-
);
|
|
16073
|
-
const { sessionId } = await result;
|
|
16074
|
-
const draft = await readContract(workspace);
|
|
16075
|
-
return { ...draft, sessionId };
|
|
16076
|
-
}
|
|
16077
|
-
async function reviseDeploySkill(opts) {
|
|
16078
|
-
const { feedback, sendLog } = opts;
|
|
16079
|
-
if (!feedback.trim()) throw new Error("Corrections are empty.");
|
|
16080
|
-
const workspace = import_path5.default.join(opts.spaceDir, "workspace");
|
|
16081
|
-
sendLog({ type: "system", content: "Applying your corrections..." });
|
|
16082
|
-
const { result } = await spawnAgent(
|
|
16083
|
-
opts.provider,
|
|
16084
|
-
hostTarget(opts.spaceDir),
|
|
16085
|
-
{
|
|
16086
|
-
cwd: workspace,
|
|
16087
|
-
prompt: buildPrompt(prompts_default.deployCreateRevise, {
|
|
16088
|
-
FEEDBACK: feedback
|
|
16089
|
-
}),
|
|
16090
|
-
allowedTools: "Read,Grep,Glob,Bash,Edit,Write,WebSearch,WebFetch",
|
|
16091
|
-
permissionMode: "auto",
|
|
16092
|
-
model: "claude-opus-4-6",
|
|
16093
|
-
includePartialMessages: true,
|
|
16094
|
-
resumeSessionId: opts.resumeSessionId || void 0,
|
|
16095
|
-
env: spaceEnv(opts.spaceDir),
|
|
16096
|
-
callbacks: { onLog: (entry) => sendLog(entry) }
|
|
17876
|
+
async onClose() {
|
|
17877
|
+
if (this.settled) return;
|
|
17878
|
+
const tail2 = this.output.trim().slice(-300);
|
|
17879
|
+
if (this.startRejecter) {
|
|
17880
|
+
this.fail(tail2 || "Codex sign-in ended unexpectedly.");
|
|
17881
|
+
return;
|
|
16097
17882
|
}
|
|
16098
|
-
|
|
16099
|
-
|
|
16100
|
-
const draft = await readContract(workspace);
|
|
16101
|
-
return { ...draft, sessionId: sessionId || opts.resumeSessionId || "" };
|
|
16102
|
-
}
|
|
16103
|
-
async function loadSkillDraft(opts) {
|
|
16104
|
-
const branch = opts.branch.replace(/[^\w./-]/g, "");
|
|
16105
|
-
if (!branch.startsWith("deploy-skill")) {
|
|
16106
|
-
throw new Error("Not a deploy-skill branch.");
|
|
17883
|
+
this.settled = true;
|
|
17884
|
+
await this.verifyAndComplete(tail2);
|
|
16107
17885
|
}
|
|
16108
|
-
|
|
16109
|
-
|
|
16110
|
-
|
|
16111
|
-
|
|
16112
|
-
|
|
16113
|
-
|
|
16114
|
-
|
|
16115
|
-
|
|
16116
|
-
|
|
16117
|
-
|
|
16118
|
-
return readContract(workspace);
|
|
16119
|
-
}
|
|
16120
|
-
async function approveDeploySkill(opts) {
|
|
16121
|
-
const workspace = import_path5.default.join(opts.spaceDir, "workspace");
|
|
16122
|
-
await readContract(workspace);
|
|
16123
|
-
await ensureClaudeSkillLink(workspace);
|
|
16124
|
-
if (opts.gitName)
|
|
16125
|
-
await hostGit(workspace, "config", "user.name", opts.gitName);
|
|
16126
|
-
if (opts.gitEmail)
|
|
16127
|
-
await hostGit(workspace, "config", "user.email", opts.gitEmail);
|
|
16128
|
-
if (opts.onBranch) {
|
|
16129
|
-
const dirty = await hostGit(workspace, "status", "--porcelain");
|
|
16130
|
-
if (dirty.trim()) {
|
|
16131
|
-
await hostGit(workspace, "add", "-A");
|
|
16132
|
-
await hostGit(workspace, "commit", "-m", "Revise deploy skill");
|
|
16133
|
-
await hostGit(workspace, "push", "origin", opts.onBranch);
|
|
17886
|
+
fail(message) {
|
|
17887
|
+
if (this.settled) return;
|
|
17888
|
+
this.settled = true;
|
|
17889
|
+
if (this.startRejecter) {
|
|
17890
|
+
const reject = this.startRejecter;
|
|
17891
|
+
this.startResolver = null;
|
|
17892
|
+
this.startRejecter = null;
|
|
17893
|
+
reject(new Error(message));
|
|
17894
|
+
void this.cleanup();
|
|
17895
|
+
return;
|
|
16134
17896
|
}
|
|
16135
|
-
|
|
17897
|
+
void this.cleanup();
|
|
17898
|
+
this.onDone({ status: "error", message });
|
|
17899
|
+
}
|
|
17900
|
+
async cleanup() {
|
|
17901
|
+
await stopLoginExec(this.target, "codex", this.spaceSlug);
|
|
17902
|
+
}
|
|
17903
|
+
reset() {
|
|
17904
|
+
this.output = "";
|
|
17905
|
+
this.strip = createAnsiStripper();
|
|
17906
|
+
this.settled = false;
|
|
17907
|
+
this.startResolver = null;
|
|
17908
|
+
this.startRejecter = null;
|
|
17909
|
+
}
|
|
17910
|
+
};
|
|
17911
|
+
|
|
17912
|
+
// ../../shared/all/helpers/board-agent-core/github-auth.ts
|
|
17913
|
+
var DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
17914
|
+
var TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
17915
|
+
var API_BASE = "https://api.github.com";
|
|
17916
|
+
var GITHUB_SCOPES = "repo read:user user:email";
|
|
17917
|
+
var DEVICE_CODE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
|
|
17918
|
+
async function startGithubDeviceCode(clientId) {
|
|
17919
|
+
const res = await fetch(DEVICE_CODE_URL, {
|
|
17920
|
+
method: "POST",
|
|
17921
|
+
headers: {
|
|
17922
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
17923
|
+
Accept: "application/json"
|
|
17924
|
+
},
|
|
17925
|
+
body: new URLSearchParams({ client_id: clientId, scope: GITHUB_SCOPES })
|
|
17926
|
+
});
|
|
17927
|
+
if (!res.ok) {
|
|
17928
|
+
throw new Error(`GitHub device-code start failed: ${await res.text()}`);
|
|
16136
17929
|
}
|
|
16137
|
-
|
|
16138
|
-
|
|
16139
|
-
|
|
16140
|
-
|
|
16141
|
-
"ls-remote",
|
|
16142
|
-
"--exit-code",
|
|
16143
|
-
"--heads",
|
|
16144
|
-
"origin",
|
|
16145
|
-
target
|
|
17930
|
+
const data = await res.json();
|
|
17931
|
+
if (data.error || !data.device_code || !data.user_code) {
|
|
17932
|
+
throw new Error(
|
|
17933
|
+
data.error_description || data.error || "GitHub returned no device code. Is device flow enabled on the OAuth App?"
|
|
16146
17934
|
);
|
|
16147
|
-
const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace(/[-:]/g, "").replace("T", "-");
|
|
16148
|
-
target = `${target}-${stamp}`;
|
|
16149
|
-
} catch {
|
|
16150
17935
|
}
|
|
16151
|
-
|
|
16152
|
-
|
|
16153
|
-
|
|
16154
|
-
|
|
16155
|
-
|
|
17936
|
+
return {
|
|
17937
|
+
deviceCode: data.device_code,
|
|
17938
|
+
userCode: data.user_code,
|
|
17939
|
+
verificationUri: data.verification_uri || "https://github.com/login/device",
|
|
17940
|
+
expiresIn: data.expires_in ?? 900,
|
|
17941
|
+
interval: data.interval ?? 5
|
|
17942
|
+
};
|
|
16156
17943
|
}
|
|
16157
|
-
async function
|
|
16158
|
-
const
|
|
16159
|
-
|
|
17944
|
+
async function pollGithubDeviceCode(clientId, deviceCode) {
|
|
17945
|
+
const res = await fetch(TOKEN_URL, {
|
|
17946
|
+
method: "POST",
|
|
17947
|
+
headers: {
|
|
17948
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
17949
|
+
Accept: "application/json"
|
|
17950
|
+
},
|
|
17951
|
+
body: new URLSearchParams({
|
|
17952
|
+
client_id: clientId,
|
|
17953
|
+
device_code: deviceCode,
|
|
17954
|
+
grant_type: DEVICE_CODE_GRANT
|
|
17955
|
+
})
|
|
17956
|
+
});
|
|
17957
|
+
const data = await res.json();
|
|
17958
|
+
if (data.access_token) {
|
|
17959
|
+
return { status: "complete", accessToken: data.access_token };
|
|
17960
|
+
}
|
|
17961
|
+
switch (data.error) {
|
|
17962
|
+
case "authorization_pending":
|
|
17963
|
+
return { status: "pending" };
|
|
17964
|
+
case "slow_down":
|
|
17965
|
+
return { status: "slow_down" };
|
|
17966
|
+
case "expired_token":
|
|
17967
|
+
return { status: "expired" };
|
|
17968
|
+
case "access_denied":
|
|
17969
|
+
return { status: "declined" };
|
|
17970
|
+
default:
|
|
17971
|
+
return {
|
|
17972
|
+
status: "error",
|
|
17973
|
+
message: data.error_description || data.error || "GitHub sign-in failed."
|
|
17974
|
+
};
|
|
17975
|
+
}
|
|
17976
|
+
}
|
|
17977
|
+
async function githubApi(token, path17) {
|
|
17978
|
+
const res = await fetch(`${API_BASE}${path17}`, {
|
|
17979
|
+
headers: {
|
|
17980
|
+
Authorization: `Bearer ${token}`,
|
|
17981
|
+
Accept: "application/vnd.github+json",
|
|
17982
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
17983
|
+
}
|
|
17984
|
+
});
|
|
17985
|
+
if (!res.ok) {
|
|
16160
17986
|
throw new Error(
|
|
16161
|
-
|
|
17987
|
+
`GitHub ${path17} failed (${res.status}): ${await res.text()}`
|
|
16162
17988
|
);
|
|
16163
17989
|
}
|
|
16164
|
-
|
|
16165
|
-
|
|
16166
|
-
|
|
16167
|
-
|
|
16168
|
-
|
|
16169
|
-
|
|
16170
|
-
|
|
16171
|
-
|
|
16172
|
-
|
|
16173
|
-
|
|
16174
|
-
|
|
16175
|
-
try {
|
|
16176
|
-
const scriptArg = tmp.split(import_path5.default.sep).join("/");
|
|
16177
|
-
const res = await execLogin(`bash ${JSON.stringify(scriptArg)}`, {
|
|
16178
|
-
cwd: workspace,
|
|
16179
|
-
env: { ...process.env, ...spaceEnv(opts.spaceDir) },
|
|
16180
|
-
timeout: 12e4
|
|
16181
|
-
});
|
|
16182
|
-
output = res.output;
|
|
16183
|
-
if (res.code !== 0) ok = false;
|
|
16184
|
-
} finally {
|
|
16185
|
-
await import_promises3.default.rm(tmp, { force: true });
|
|
16186
|
-
}
|
|
16187
|
-
const checks = [];
|
|
16188
|
-
for (const line of output.split("\n")) {
|
|
16189
|
-
const match = /^(OK|FAIL):\s*(.+)$/.exec(line.trim());
|
|
16190
|
-
if (!match) continue;
|
|
16191
|
-
const [, verdict, rest] = match;
|
|
16192
|
-
const dash = rest.indexOf(" - ");
|
|
16193
|
-
checks.push({
|
|
16194
|
-
ok: verdict === "OK",
|
|
16195
|
-
label: dash > 0 ? rest.slice(0, dash) : rest,
|
|
16196
|
-
detail: dash > 0 ? rest.slice(dash + 3) : ""
|
|
16197
|
-
});
|
|
16198
|
-
if (verdict === "FAIL") ok = false;
|
|
17990
|
+
return await res.json();
|
|
17991
|
+
}
|
|
17992
|
+
async function fetchGithubIdentity(token) {
|
|
17993
|
+
const user = await githubApi(token, "/user");
|
|
17994
|
+
let email = user.email ?? "";
|
|
17995
|
+
if (!email) {
|
|
17996
|
+
try {
|
|
17997
|
+
const emails = await githubApi(token, "/user/emails");
|
|
17998
|
+
email = emails.find((e) => e.primary && e.verified)?.email ?? emails.find((e) => e.verified)?.email ?? "";
|
|
17999
|
+
} catch {
|
|
18000
|
+
}
|
|
16199
18001
|
}
|
|
16200
|
-
|
|
18002
|
+
if (!email) email = `${user.id}+${user.login}@users.noreply.github.com`;
|
|
18003
|
+
return { login: user.login, name: user.name || user.login, email };
|
|
16201
18004
|
}
|
|
16202
|
-
async function
|
|
16203
|
-
const
|
|
16204
|
-
|
|
16205
|
-
|
|
16206
|
-
|
|
16207
|
-
|
|
16208
|
-
|
|
16209
|
-
|
|
16210
|
-
|
|
16211
|
-
|
|
16212
|
-
|
|
16213
|
-
|
|
16214
|
-
|
|
16215
|
-
sendLog({
|
|
16216
|
-
type: "system",
|
|
16217
|
-
content: "Setting this machine up to pass the environment check..."
|
|
16218
|
-
});
|
|
16219
|
-
const { result } = await spawnAgent(
|
|
16220
|
-
opts.provider,
|
|
16221
|
-
hostTarget(opts.spaceDir),
|
|
16222
|
-
{
|
|
16223
|
-
cwd: workspace,
|
|
16224
|
-
prompt: buildPrompt(prompts_default.deployEnvFix, {
|
|
16225
|
-
CHECKS: failed || before.output.slice(-2e3),
|
|
16226
|
-
SCRIPT: script
|
|
16227
|
-
}),
|
|
16228
|
-
allowedTools: "Bash,Read,Grep,Glob,WebSearch,WebFetch",
|
|
16229
|
-
// auto: installs are in-scope of the owner's explicit "fix environment"
|
|
16230
|
-
// request; the classifier still blocks anything beyond it.
|
|
16231
|
-
permissionMode: "auto",
|
|
16232
|
-
model: "claude-opus-4-6",
|
|
16233
|
-
includePartialMessages: true,
|
|
16234
|
-
env: spaceEnv(opts.spaceDir),
|
|
16235
|
-
callbacks: { onLog: (entry) => sendLog(entry) }
|
|
18005
|
+
async function listGithubRepos(token) {
|
|
18006
|
+
const out = [];
|
|
18007
|
+
for (let page = 1; page <= 3; page++) {
|
|
18008
|
+
const batch = await githubApi(token, `/user/repos?per_page=100&sort=pushed&page=${page}`);
|
|
18009
|
+
for (const r of batch) {
|
|
18010
|
+
if (r.permissions && !r.permissions.push) continue;
|
|
18011
|
+
out.push({
|
|
18012
|
+
fullName: r.full_name,
|
|
18013
|
+
// Strip any embedded credentials; buildCloneUrl adds the token.
|
|
18014
|
+
cloneUrl: r.clone_url.replace(/\/\/[^@/]*@/, "//"),
|
|
18015
|
+
defaultBranch: r.default_branch,
|
|
18016
|
+
private: r.private
|
|
18017
|
+
});
|
|
16236
18018
|
}
|
|
16237
|
-
|
|
16238
|
-
|
|
16239
|
-
|
|
16240
|
-
return runEnvironmentCheck(opts);
|
|
18019
|
+
if (batch.length < 100) break;
|
|
18020
|
+
}
|
|
18021
|
+
return out;
|
|
16241
18022
|
}
|
|
16242
|
-
async function
|
|
16243
|
-
const
|
|
16244
|
-
|
|
16245
|
-
|
|
16246
|
-
|
|
18023
|
+
async function listGithubBranches(token, fullName) {
|
|
18024
|
+
const branches = await githubApi(
|
|
18025
|
+
token,
|
|
18026
|
+
`/repos/${fullName}/branches?per_page=100`
|
|
18027
|
+
);
|
|
18028
|
+
return branches.map((b) => b.name);
|
|
16247
18029
|
}
|
|
16248
18030
|
|
|
16249
18031
|
// ../../shared/all/helpers/board-agent-core/host-run.ts
|
|
16250
|
-
var
|
|
18032
|
+
var import_path8 = __toESM(require("path"));
|
|
16251
18033
|
|
|
16252
18034
|
// ../../shared/all/helpers/board-agent-core/host-secrets.ts
|
|
16253
18035
|
var import_child_process4 = require("child_process");
|
|
16254
18036
|
var import_crypto2 = __toESM(require("crypto"));
|
|
16255
|
-
var
|
|
16256
|
-
var
|
|
18037
|
+
var import_promises5 = __toESM(require("fs/promises"));
|
|
18038
|
+
var import_path7 = __toESM(require("path"));
|
|
16257
18039
|
var import_util6 = require("util");
|
|
16258
18040
|
var execFileAsync3 = (0, import_util6.promisify)(import_child_process4.execFile);
|
|
16259
18041
|
var IDENTITY_FILE = "age-identity.enc";
|
|
@@ -16286,7 +18068,7 @@ function deriveKey(password, salt) {
|
|
|
16286
18068
|
}
|
|
16287
18069
|
async function secretsInitialized(configDir) {
|
|
16288
18070
|
try {
|
|
16289
|
-
await
|
|
18071
|
+
await import_promises5.default.access(import_path7.default.join(configDir, IDENTITY_FILE));
|
|
16290
18072
|
return true;
|
|
16291
18073
|
} catch {
|
|
16292
18074
|
return false;
|
|
@@ -16325,10 +18107,10 @@ async function initSecrets(configDir, password) {
|
|
|
16325
18107
|
tag: cipher.getAuthTag().toString("base64"),
|
|
16326
18108
|
data: data.toString("base64")
|
|
16327
18109
|
};
|
|
16328
|
-
await
|
|
18110
|
+
await import_promises5.default.writeFile(import_path7.default.join(configDir, IDENTITY_FILE), JSON.stringify(box), {
|
|
16329
18111
|
mode: 384
|
|
16330
18112
|
});
|
|
16331
|
-
await
|
|
18113
|
+
await import_promises5.default.writeFile(import_path7.default.join(configDir, RECIPIENT_FILE), `${recipient}
|
|
16332
18114
|
`, {
|
|
16333
18115
|
mode: 384
|
|
16334
18116
|
});
|
|
@@ -16351,17 +18133,17 @@ function decryptIdentity(box, password) {
|
|
|
16351
18133
|
}
|
|
16352
18134
|
}
|
|
16353
18135
|
function envPath(spaceDir) {
|
|
16354
|
-
return
|
|
18136
|
+
return import_path7.default.join(spaceDir, "secrets", ENV_FILE);
|
|
16355
18137
|
}
|
|
16356
18138
|
async function identityFor(configDir, password) {
|
|
16357
18139
|
const box = JSON.parse(
|
|
16358
|
-
await
|
|
18140
|
+
await import_promises5.default.readFile(import_path7.default.join(configDir, IDENTITY_FILE), "utf-8")
|
|
16359
18141
|
);
|
|
16360
18142
|
return decryptIdentity(box, password);
|
|
16361
18143
|
}
|
|
16362
18144
|
async function readEnv(spaceDir, identity) {
|
|
16363
18145
|
try {
|
|
16364
|
-
await
|
|
18146
|
+
await import_promises5.default.access(envPath(spaceDir));
|
|
16365
18147
|
} catch {
|
|
16366
18148
|
return {};
|
|
16367
18149
|
}
|
|
@@ -16390,14 +18172,14 @@ async function readEnv(spaceDir, identity) {
|
|
|
16390
18172
|
return values;
|
|
16391
18173
|
}
|
|
16392
18174
|
async function writeEnv(configDir, spaceDir, identity, values) {
|
|
16393
|
-
const recipient = (await
|
|
18175
|
+
const recipient = (await import_promises5.default.readFile(import_path7.default.join(configDir, RECIPIENT_FILE), "utf-8")).trim();
|
|
16394
18176
|
const plain = `${Object.entries(values).map(([k, v]) => `${k}=${v}`).join("\n")}
|
|
16395
18177
|
`;
|
|
16396
18178
|
const tmp = envPath(spaceDir).replace(
|
|
16397
18179
|
/\.enc\.env$/,
|
|
16398
18180
|
`.${process.pid}.tmp.env`
|
|
16399
18181
|
);
|
|
16400
|
-
await
|
|
18182
|
+
await import_promises5.default.writeFile(tmp, plain, { mode: 384 });
|
|
16401
18183
|
try {
|
|
16402
18184
|
const { stdout } = await execFileAsync3(
|
|
16403
18185
|
"sops",
|
|
@@ -16416,9 +18198,9 @@ async function writeEnv(configDir, spaceDir, identity, values) {
|
|
|
16416
18198
|
maxBuffer: 4 * 1024 * 1024
|
|
16417
18199
|
}
|
|
16418
18200
|
);
|
|
16419
|
-
await
|
|
18201
|
+
await import_promises5.default.writeFile(envPath(spaceDir), stdout, { mode: 384 });
|
|
16420
18202
|
} finally {
|
|
16421
|
-
await
|
|
18203
|
+
await import_promises5.default.rm(tmp, { force: true });
|
|
16422
18204
|
}
|
|
16423
18205
|
}
|
|
16424
18206
|
async function listSecretKeys(configDir, spaceDir, password) {
|
|
@@ -16484,8 +18266,8 @@ function tailMarker(text) {
|
|
|
16484
18266
|
}
|
|
16485
18267
|
async function startDeployRun(opts) {
|
|
16486
18268
|
const { hooks } = opts;
|
|
16487
|
-
const workspace =
|
|
16488
|
-
const configDir =
|
|
18269
|
+
const workspace = import_path8.default.join(opts.spaceDir, "workspace");
|
|
18270
|
+
const configDir = import_path8.default.dirname(opts.spaceDir);
|
|
16489
18271
|
const status = await checkDeployStatus(opts);
|
|
16490
18272
|
if (!status.hasSkill || !status.hasVariables || !status.hasChecker) {
|
|
16491
18273
|
throw new Error(
|
|
@@ -17365,6 +19147,48 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
17365
19147
|
githubToken: config.githubToken
|
|
17366
19148
|
});
|
|
17367
19149
|
}
|
|
19150
|
+
// ── Agent skills (SKILL.md) ──
|
|
19151
|
+
// Repo opts are optional here, unlike deploy: the global roots are readable
|
|
19152
|
+
// with no repo configured, and listSkills reports repoAvailable: false.
|
|
19153
|
+
skillOpts() {
|
|
19154
|
+
const config = this.core.readConfig();
|
|
19155
|
+
return {
|
|
19156
|
+
spaceDir: this.core.spaceDir(),
|
|
19157
|
+
repoUrl: config.repoUrl,
|
|
19158
|
+
mainBranch: config.mainBranch,
|
|
19159
|
+
githubToken: config.githubToken
|
|
19160
|
+
};
|
|
19161
|
+
}
|
|
19162
|
+
skillsList() {
|
|
19163
|
+
return listSkills(this.skillOpts());
|
|
19164
|
+
}
|
|
19165
|
+
skillRead(payload) {
|
|
19166
|
+
return readSkill({ ...this.skillOpts(), ...payload });
|
|
19167
|
+
}
|
|
19168
|
+
skillWriteGlobal(payload) {
|
|
19169
|
+
return writeGlobalSkill(payload);
|
|
19170
|
+
}
|
|
19171
|
+
skillDeleteGlobal(payload) {
|
|
19172
|
+
return deleteGlobalSkill(payload);
|
|
19173
|
+
}
|
|
19174
|
+
skillMigrate(payload) {
|
|
19175
|
+
const config = this.core.readConfig();
|
|
19176
|
+
return migrateSkills({
|
|
19177
|
+
...this.skillOpts(),
|
|
19178
|
+
...payload,
|
|
19179
|
+
gitName: config.gitName,
|
|
19180
|
+
gitEmail: config.gitEmail
|
|
19181
|
+
});
|
|
19182
|
+
}
|
|
19183
|
+
skillProposeRepo(payload) {
|
|
19184
|
+
const config = this.core.readConfig();
|
|
19185
|
+
return proposeRepoSkill({
|
|
19186
|
+
...this.skillOpts(),
|
|
19187
|
+
...payload,
|
|
19188
|
+
gitName: config.gitName,
|
|
19189
|
+
gitEmail: config.gitEmail
|
|
19190
|
+
});
|
|
19191
|
+
}
|
|
17368
19192
|
deployCreatorState() {
|
|
17369
19193
|
return this.creatorState;
|
|
17370
19194
|
}
|
|
@@ -17747,7 +19571,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
17747
19571
|
// ── Runner secrets store (secrets.sh contract; see host-secrets.ts) ──
|
|
17748
19572
|
secretsDirs() {
|
|
17749
19573
|
const spaceDir = this.core.spaceDir();
|
|
17750
|
-
return { configDir: (0,
|
|
19574
|
+
return { configDir: (0, import_path9.dirname)(spaceDir), spaceDir };
|
|
17751
19575
|
}
|
|
17752
19576
|
async deploySecretsStatus() {
|
|
17753
19577
|
return {
|
|
@@ -18115,6 +19939,21 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
18115
19939
|
bareGitStatus(p) {
|
|
18116
19940
|
return this.terminal.bareGitStatus(p);
|
|
18117
19941
|
}
|
|
19942
|
+
bareGitCommitDiff(p) {
|
|
19943
|
+
return this.terminal.bareGitCommitDiff(p);
|
|
19944
|
+
}
|
|
19945
|
+
bareGitStage(p) {
|
|
19946
|
+
return this.terminal.bareGitStage(p);
|
|
19947
|
+
}
|
|
19948
|
+
bareGitUnstage(p) {
|
|
19949
|
+
return this.terminal.bareGitUnstage(p);
|
|
19950
|
+
}
|
|
19951
|
+
bareListProcesses(p) {
|
|
19952
|
+
return this.terminal.bareListProcesses(p);
|
|
19953
|
+
}
|
|
19954
|
+
bareKillProcess(p) {
|
|
19955
|
+
return this.terminal.bareKillProcess(p);
|
|
19956
|
+
}
|
|
18118
19957
|
bareListTerminals(p) {
|
|
18119
19958
|
return this.terminal.bareListTerminals(
|
|
18120
19959
|
p,
|
|
@@ -18537,6 +20376,28 @@ ${c.content.trim() || "(no description)"}`
|
|
|
18537
20376
|
return await this.bareWorkNames();
|
|
18538
20377
|
case "deployStatus":
|
|
18539
20378
|
return await this.deployStatus();
|
|
20379
|
+
case "skillsList":
|
|
20380
|
+
return await this.skillsList();
|
|
20381
|
+
case "skillRead":
|
|
20382
|
+
return await this.skillRead(
|
|
20383
|
+
payload
|
|
20384
|
+
);
|
|
20385
|
+
case "skillWriteGlobal":
|
|
20386
|
+
return await this.skillWriteGlobal(
|
|
20387
|
+
payload
|
|
20388
|
+
);
|
|
20389
|
+
case "skillDeleteGlobal":
|
|
20390
|
+
return await this.skillDeleteGlobal(
|
|
20391
|
+
payload
|
|
20392
|
+
);
|
|
20393
|
+
case "skillMigrate":
|
|
20394
|
+
return await this.skillMigrate(
|
|
20395
|
+
payload
|
|
20396
|
+
);
|
|
20397
|
+
case "skillProposeRepo":
|
|
20398
|
+
return await this.skillProposeRepo(
|
|
20399
|
+
payload
|
|
20400
|
+
);
|
|
18540
20401
|
case "deployCreateSkill":
|
|
18541
20402
|
return await this.deployCreateSkill(
|
|
18542
20403
|
payload,
|
|
@@ -18700,6 +20561,27 @@ ${c.content.trim() || "(no description)"}`
|
|
|
18700
20561
|
return await this.bareGitStatus(
|
|
18701
20562
|
payload
|
|
18702
20563
|
);
|
|
20564
|
+
case "bareGitCommitDiff":
|
|
20565
|
+
return await this.bareGitCommitDiff(
|
|
20566
|
+
payload
|
|
20567
|
+
);
|
|
20568
|
+
case "bareGitStage":
|
|
20569
|
+
return await this.bareGitStage(
|
|
20570
|
+
payload
|
|
20571
|
+
);
|
|
20572
|
+
case "bareGitUnstage":
|
|
20573
|
+
return await this.bareGitUnstage(
|
|
20574
|
+
payload
|
|
20575
|
+
);
|
|
20576
|
+
case "bareListProcesses":
|
|
20577
|
+
return await this.bareListProcesses(
|
|
20578
|
+
payload
|
|
20579
|
+
);
|
|
20580
|
+
case "bareKillProcess":
|
|
20581
|
+
await this.bareKillProcess(
|
|
20582
|
+
payload
|
|
20583
|
+
);
|
|
20584
|
+
return void 0;
|
|
18703
20585
|
case "bareListTerminals":
|
|
18704
20586
|
return await this.bareListTerminals(
|
|
18705
20587
|
payload
|
|
@@ -18733,7 +20615,7 @@ ${c.content.trim() || "(no description)"}`
|
|
|
18733
20615
|
);
|
|
18734
20616
|
return void 0;
|
|
18735
20617
|
case "bareListExposed":
|
|
18736
|
-
return this.bareListExposed(
|
|
20618
|
+
return await this.bareListExposed(
|
|
18737
20619
|
payload
|
|
18738
20620
|
);
|
|
18739
20621
|
case "secureStatus":
|
|
@@ -18748,9 +20630,9 @@ ${c.content.trim() || "(no description)"}`
|
|
|
18748
20630
|
var import_child_process5 = require("child_process");
|
|
18749
20631
|
var import_crypto3 = require("crypto");
|
|
18750
20632
|
var import_fs3 = require("fs");
|
|
18751
|
-
var
|
|
20633
|
+
var import_promises6 = __toESM(require("fs/promises"));
|
|
18752
20634
|
var import_net2 = require("net");
|
|
18753
|
-
var
|
|
20635
|
+
var import_path10 = require("path");
|
|
18754
20636
|
var import_util7 = require("util");
|
|
18755
20637
|
var execFileAsync4 = (0, import_util7.promisify)(import_child_process5.execFile);
|
|
18756
20638
|
var IndexService = class {
|
|
@@ -18809,7 +20691,7 @@ var IndexService = class {
|
|
|
18809
20691
|
const log = await this.openLog();
|
|
18810
20692
|
this.proc = (0, import_child_process5.spawn)(
|
|
18811
20693
|
process.execPath,
|
|
18812
|
-
[(0,
|
|
20694
|
+
[(0, import_path10.join)(this.opts.indexDir, "index.js")],
|
|
18813
20695
|
{
|
|
18814
20696
|
cwd: this.opts.indexDir,
|
|
18815
20697
|
env: {
|
|
@@ -18837,8 +20719,8 @@ var IndexService = class {
|
|
|
18837
20719
|
* opened (a missing log must never stop the service from starting). */
|
|
18838
20720
|
async openLog() {
|
|
18839
20721
|
try {
|
|
18840
|
-
await
|
|
18841
|
-
return (0, import_fs3.openSync)((0,
|
|
20722
|
+
await import_promises6.default.mkdir(this.opts.dataDir, { recursive: true });
|
|
20723
|
+
return (0, import_fs3.openSync)((0, import_path10.join)(this.opts.dataDir, "index.log"), "a");
|
|
18842
20724
|
} catch {
|
|
18843
20725
|
return null;
|
|
18844
20726
|
}
|
|
@@ -18847,10 +20729,10 @@ var IndexService = class {
|
|
|
18847
20729
|
* platform needing node-gyp fails here rather than at query time. */
|
|
18848
20730
|
async installDeps() {
|
|
18849
20731
|
const dir = this.opts.indexDir;
|
|
18850
|
-
if ((0, import_fs3.existsSync)((0,
|
|
18851
|
-
await
|
|
18852
|
-
(0,
|
|
18853
|
-
(0,
|
|
20732
|
+
if ((0, import_fs3.existsSync)((0, import_path10.join)(dir, "node_modules"))) return;
|
|
20733
|
+
await import_promises6.default.copyFile(
|
|
20734
|
+
(0, import_path10.join)(dir, "image-package.json"),
|
|
20735
|
+
(0, import_path10.join)(dir, "package.json")
|
|
18854
20736
|
);
|
|
18855
20737
|
await execFileAsync4(
|
|
18856
20738
|
"npm",
|
|
@@ -18862,15 +20744,15 @@ var IndexService = class {
|
|
|
18862
20744
|
* CLIs' persistent MCP config, so a fresh port each start would leave every
|
|
18863
20745
|
* registration pointing at a dead one. */
|
|
18864
20746
|
async stablePort() {
|
|
18865
|
-
const file = (0,
|
|
18866
|
-
const saved = Number(await
|
|
20747
|
+
const file = (0, import_path10.join)(this.opts.dataDir, ".port");
|
|
20748
|
+
const saved = Number(await import_promises6.default.readFile(file, "utf-8").catch(() => ""));
|
|
18867
20749
|
if (Number.isInteger(saved) && saved > 0 && await isFree(saved)) {
|
|
18868
20750
|
return saved;
|
|
18869
20751
|
}
|
|
18870
20752
|
const port = await freePort();
|
|
18871
|
-
await
|
|
20753
|
+
await import_promises6.default.mkdir(this.opts.dataDir, { recursive: true }).catch(() => {
|
|
18872
20754
|
});
|
|
18873
|
-
await
|
|
20755
|
+
await import_promises6.default.writeFile(file, String(port), "utf-8").catch(() => {
|
|
18874
20756
|
});
|
|
18875
20757
|
return port;
|
|
18876
20758
|
}
|
|
@@ -19038,24 +20920,24 @@ async function pairWithBrowser(serverUrl) {
|
|
|
19038
20920
|
|
|
19039
20921
|
// src/config.ts
|
|
19040
20922
|
var import_fs7 = __toESM(require("fs"));
|
|
19041
|
-
var
|
|
19042
|
-
var
|
|
20923
|
+
var import_os5 = __toESM(require("os"));
|
|
20924
|
+
var import_path15 = __toESM(require("path"));
|
|
19043
20925
|
|
|
19044
20926
|
// src/secureFile.ts
|
|
19045
20927
|
var import_crypto6 = __toESM(require("crypto"));
|
|
19046
20928
|
var import_fs6 = __toESM(require("fs"));
|
|
19047
|
-
var
|
|
20929
|
+
var import_path14 = __toESM(require("path"));
|
|
19048
20930
|
|
|
19049
20931
|
// src/keychain.ts
|
|
19050
20932
|
var import_child_process7 = require("child_process");
|
|
19051
20933
|
var import_crypto4 = __toESM(require("crypto"));
|
|
19052
20934
|
var import_fs4 = __toESM(require("fs"));
|
|
19053
|
-
var
|
|
19054
|
-
var
|
|
20935
|
+
var import_os3 = __toESM(require("os"));
|
|
20936
|
+
var import_path11 = __toESM(require("path"));
|
|
19055
20937
|
var SERVICE = "factiii-runner";
|
|
19056
20938
|
var ACCOUNT = "config-encryption-key";
|
|
19057
|
-
var DPAPI_KEY_FILE =
|
|
19058
|
-
|
|
20939
|
+
var DPAPI_KEY_FILE = import_path11.default.join(
|
|
20940
|
+
import_os3.default.homedir(),
|
|
19059
20941
|
".factiii-runner",
|
|
19060
20942
|
"config-key.dpapi"
|
|
19061
20943
|
);
|
|
@@ -19160,7 +21042,7 @@ function winWrite(key) {
|
|
|
19160
21042
|
"-Command",
|
|
19161
21043
|
`ConvertTo-SecureString -String '${key.toString("base64")}' -AsPlainText -Force | ConvertFrom-SecureString`
|
|
19162
21044
|
]);
|
|
19163
|
-
import_fs4.default.mkdirSync(
|
|
21045
|
+
import_fs4.default.mkdirSync(import_path11.default.dirname(DPAPI_KEY_FILE), { recursive: true });
|
|
19164
21046
|
import_fs4.default.writeFileSync(DPAPI_KEY_FILE, `${out.trim()}
|
|
19165
21047
|
`, { mode: 384 });
|
|
19166
21048
|
}
|
|
@@ -19324,21 +21206,21 @@ function keychainStatus() {
|
|
|
19324
21206
|
// src/secureStore.ts
|
|
19325
21207
|
var import_crypto5 = __toESM(require("crypto"));
|
|
19326
21208
|
var import_fs5 = __toESM(require("fs"));
|
|
19327
|
-
var
|
|
21209
|
+
var import_path13 = __toESM(require("path"));
|
|
19328
21210
|
|
|
19329
21211
|
// src/paths.ts
|
|
19330
|
-
var
|
|
19331
|
-
var
|
|
19332
|
-
var CONFIG_DIR =
|
|
21212
|
+
var import_os4 = __toESM(require("os"));
|
|
21213
|
+
var import_path12 = __toESM(require("path"));
|
|
21214
|
+
var CONFIG_DIR = import_path12.default.join(import_os4.default.homedir(), ".factiii-runner");
|
|
19333
21215
|
function safeSlug(spaceSlug) {
|
|
19334
21216
|
return spaceSlug.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
19335
21217
|
}
|
|
19336
21218
|
function spaceDirPath(spaceSlug) {
|
|
19337
|
-
return
|
|
21219
|
+
return import_path12.default.join(CONFIG_DIR, safeSlug(spaceSlug));
|
|
19338
21220
|
}
|
|
19339
21221
|
|
|
19340
21222
|
// src/secureStore.ts
|
|
19341
|
-
var VAULT_KEY_FILE =
|
|
21223
|
+
var VAULT_KEY_FILE = import_path13.default.join(CONFIG_DIR, "vault-key.json");
|
|
19342
21224
|
var SCRYPT_N2 = 1 << 15;
|
|
19343
21225
|
var MIN_PASSWORD_LENGTH = 8;
|
|
19344
21226
|
var SecureStoreError = class extends Error {
|
|
@@ -19660,7 +21542,7 @@ function parseEnvelope(raw) {
|
|
|
19660
21542
|
}
|
|
19661
21543
|
}
|
|
19662
21544
|
function atomicWrite(filePath, body) {
|
|
19663
|
-
import_fs6.default.mkdirSync(
|
|
21545
|
+
import_fs6.default.mkdirSync(import_path14.default.dirname(filePath), { recursive: true });
|
|
19664
21546
|
const tmp = `${filePath}.${process.pid}.tmp`;
|
|
19665
21547
|
import_fs6.default.writeFileSync(tmp, body, { mode: 384 });
|
|
19666
21548
|
import_fs6.default.renameSync(tmp, filePath);
|
|
@@ -19775,8 +21657,8 @@ function writeBootJson(filePath, value2) {
|
|
|
19775
21657
|
}
|
|
19776
21658
|
|
|
19777
21659
|
// src/config.ts
|
|
19778
|
-
var CONFIG_DIR2 =
|
|
19779
|
-
var CONFIG_FILE =
|
|
21660
|
+
var CONFIG_DIR2 = import_path15.default.join(import_os5.default.homedir(), ".factiii-runner");
|
|
21661
|
+
var CONFIG_FILE = import_path15.default.join(CONFIG_DIR2, "config.json");
|
|
19780
21662
|
function readRunnerConfig() {
|
|
19781
21663
|
return readBootJson(CONFIG_FILE);
|
|
19782
21664
|
}
|
|
@@ -19948,7 +21830,7 @@ function makeChunkReassembler() {
|
|
|
19948
21830
|
var import_crypto7 = require("crypto");
|
|
19949
21831
|
var import_fs9 = __toESM(require("fs"));
|
|
19950
21832
|
var import_node_datachannel = require("node-datachannel");
|
|
19951
|
-
var
|
|
21833
|
+
var import_path17 = __toESM(require("path"));
|
|
19952
21834
|
|
|
19953
21835
|
// ../../node_modules/engine.io-client/build/esm-debug/transports/polling-xhr.node.js
|
|
19954
21836
|
var XMLHttpRequestModule = __toESM(require_XMLHttpRequest(), 1);
|
|
@@ -21128,12 +23010,12 @@ function parse2(str) {
|
|
|
21128
23010
|
uri.queryKey = queryKey(uri, uri["query"]);
|
|
21129
23011
|
return uri;
|
|
21130
23012
|
}
|
|
21131
|
-
function pathNames(obj,
|
|
21132
|
-
const regx = /\/{2,9}/g, names =
|
|
21133
|
-
if (
|
|
23013
|
+
function pathNames(obj, path17) {
|
|
23014
|
+
const regx = /\/{2,9}/g, names = path17.replace(regx, "/").split("/");
|
|
23015
|
+
if (path17.slice(0, 1) == "/" || path17.length === 0) {
|
|
21134
23016
|
names.splice(0, 1);
|
|
21135
23017
|
}
|
|
21136
|
-
if (
|
|
23018
|
+
if (path17.slice(-1) == "/") {
|
|
21137
23019
|
names.splice(names.length - 1, 1);
|
|
21138
23020
|
}
|
|
21139
23021
|
return names;
|
|
@@ -21752,7 +23634,7 @@ var protocol2 = Socket.protocol;
|
|
|
21752
23634
|
// ../../node_modules/socket.io-client/build/esm-debug/url.js
|
|
21753
23635
|
var import_debug7 = __toESM(require_src(), 1);
|
|
21754
23636
|
var debug7 = (0, import_debug7.default)("socket.io-client:url");
|
|
21755
|
-
function url(uri,
|
|
23637
|
+
function url(uri, path17 = "", loc) {
|
|
21756
23638
|
let obj = uri;
|
|
21757
23639
|
loc = loc || typeof location !== "undefined" && location;
|
|
21758
23640
|
if (null == uri)
|
|
@@ -21786,7 +23668,7 @@ function url(uri, path16 = "", loc) {
|
|
|
21786
23668
|
obj.path = obj.path || "/";
|
|
21787
23669
|
const ipv6 = obj.host.indexOf(":") !== -1;
|
|
21788
23670
|
const host = ipv6 ? "[" + obj.host + "]" : obj.host;
|
|
21789
|
-
obj.id = obj.protocol + "://" + host + ":" + obj.port +
|
|
23671
|
+
obj.id = obj.protocol + "://" + host + ":" + obj.port + path17;
|
|
21790
23672
|
obj.href = obj.protocol + "://" + host + (loc && loc.port === obj.port ? "" : ":" + obj.port);
|
|
21791
23673
|
return obj;
|
|
21792
23674
|
}
|
|
@@ -23420,8 +25302,8 @@ function lookup(uri, opts) {
|
|
|
23420
25302
|
const parsed = url(uri, opts.path || "/socket.io");
|
|
23421
25303
|
const source = parsed.source;
|
|
23422
25304
|
const id = parsed.id;
|
|
23423
|
-
const
|
|
23424
|
-
const sameNamespace = cache[id] &&
|
|
25305
|
+
const path17 = parsed.path;
|
|
25306
|
+
const sameNamespace = cache[id] && path17 in cache[id]["nsps"];
|
|
23425
25307
|
const newConnection = opts.forceNew || opts["force new connection"] || false === opts.multiplex || sameNamespace;
|
|
23426
25308
|
let io;
|
|
23427
25309
|
if (newConnection) {
|
|
@@ -23463,8 +25345,8 @@ function cardContextPath(target, postId) {
|
|
|
23463
25345
|
const safe = postId.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80) || "card";
|
|
23464
25346
|
return `${target.paths.root}/.cards/${safe}.md`;
|
|
23465
25347
|
}
|
|
23466
|
-
function cardContextPrompt(
|
|
23467
|
-
return `Read ${
|
|
25348
|
+
function cardContextPrompt(path17) {
|
|
25349
|
+
return `Read ${path17} - it is the Factiii card this session was opened from. Use it as the context and start working on it.`;
|
|
23468
25350
|
}
|
|
23469
25351
|
var CLAUDE_PERMISSION_MODE = `--permission-mode auto`;
|
|
23470
25352
|
var RESUME_SEED = "You are now in an interactive terminal, not the board UI. Respond in plain text and use your normal tools; do not use openui-lang or component schemas anymore.";
|
|
@@ -23629,10 +25511,10 @@ var TerminalManager = class {
|
|
|
23629
25511
|
|
|
23630
25512
|
// src/agent-adapter.ts
|
|
23631
25513
|
var import_fs8 = __toESM(require("fs"));
|
|
23632
|
-
var
|
|
23633
|
-
var RUNNER_CONFIG_PATH =
|
|
25514
|
+
var import_path16 = __toESM(require("path"));
|
|
25515
|
+
var RUNNER_CONFIG_PATH = import_path16.default.join(CONFIG_DIR, "runner-config.json");
|
|
23634
25516
|
function connectionPath(spaceSlug) {
|
|
23635
|
-
return
|
|
25517
|
+
return import_path16.default.join(CONFIG_DIR, `space-${safeSlug(spaceSlug)}.onedrive.json`);
|
|
23636
25518
|
}
|
|
23637
25519
|
function writeJson(filePath, value2) {
|
|
23638
25520
|
writeSecureJson(filePath, value2);
|
|
@@ -23666,7 +25548,7 @@ function migrateLegacyConfigs() {
|
|
|
23666
25548
|
for (const name of legacy) {
|
|
23667
25549
|
const slug = name.slice("space-".length, -".json".length);
|
|
23668
25550
|
const parsed = readSecureJson(
|
|
23669
|
-
|
|
25551
|
+
import_path16.default.join(CONFIG_DIR, name)
|
|
23670
25552
|
);
|
|
23671
25553
|
if (!parsed) continue;
|
|
23672
25554
|
if (!file.defaultSlug) {
|
|
@@ -23691,7 +25573,7 @@ function encryptStrayPlaintextConfigs() {
|
|
|
23691
25573
|
return;
|
|
23692
25574
|
}
|
|
23693
25575
|
for (const name of names) {
|
|
23694
|
-
const filePath =
|
|
25576
|
+
const filePath = import_path16.default.join(CONFIG_DIR, name);
|
|
23695
25577
|
try {
|
|
23696
25578
|
const raw = import_fs8.default.readFileSync(filePath, "utf-8");
|
|
23697
25579
|
if (!raw.startsWith("{")) continue;
|
|
@@ -23820,7 +25702,7 @@ var localConfigProvider = {
|
|
|
23820
25702
|
"claude",
|
|
23821
25703
|
"codex"
|
|
23822
25704
|
]) {
|
|
23823
|
-
import_fs8.default.mkdirSync(
|
|
25705
|
+
import_fs8.default.mkdirSync(import_path16.default.join(dir, sub), { recursive: true });
|
|
23824
25706
|
}
|
|
23825
25707
|
return dir;
|
|
23826
25708
|
}
|
|
@@ -23842,6 +25724,7 @@ function toRtcIceServers(servers) {
|
|
|
23842
25724
|
continue;
|
|
23843
25725
|
}
|
|
23844
25726
|
const [, scheme, hostname, port, transport] = turn;
|
|
25727
|
+
if (scheme === "turns" || transport === "tcp") continue;
|
|
23845
25728
|
out.push({
|
|
23846
25729
|
hostname,
|
|
23847
25730
|
port: Number(port ?? (scheme === "turns" ? 5349 : 3478)),
|
|
@@ -23993,15 +25876,15 @@ async function startDaemon(config) {
|
|
|
23993
25876
|
}
|
|
23994
25877
|
);
|
|
23995
25878
|
const indexCandidates = [
|
|
23996
|
-
|
|
23997
|
-
|
|
25879
|
+
import_path17.default.join(__dirname, "..", "index"),
|
|
25880
|
+
import_path17.default.join(__dirname, "..", "..", "index", "image")
|
|
23998
25881
|
];
|
|
23999
25882
|
const indexDir = indexCandidates.find((p) => import_fs9.default.existsSync(p)) ?? indexCandidates[0];
|
|
24000
25883
|
const odIndex = new IndexService({
|
|
24001
25884
|
controlToken: (0, import_crypto7.createHash)("sha256").update(config.authToken).digest("hex"),
|
|
24002
25885
|
embedUrl: () => config.serverUrl,
|
|
24003
25886
|
indexDir,
|
|
24004
|
-
dataDir:
|
|
25887
|
+
dataDir: import_path17.default.join(CONFIG_DIR, "index-data")
|
|
24005
25888
|
});
|
|
24006
25889
|
void odIndex.ensureRunning().catch((err) => {
|
|
24007
25890
|
console.error(
|
|
@@ -24151,9 +26034,37 @@ async function startDaemon(config) {
|
|
|
24151
26034
|
).join(", ")}`
|
|
24152
26035
|
);
|
|
24153
26036
|
const peer = new import_node_datachannel.PeerConnection("runner", { iceServers });
|
|
26037
|
+
const view = {
|
|
26038
|
+
local: /* @__PURE__ */ new Set(),
|
|
26039
|
+
remote: /* @__PURE__ */ new Set(),
|
|
26040
|
+
relays: 0,
|
|
26041
|
+
gathering: "new",
|
|
26042
|
+
ice: "new"
|
|
26043
|
+
};
|
|
26044
|
+
const candidateType = (candidate) => /\btyp (\w+)/.exec(candidate)?.[1] ?? "unknown";
|
|
26045
|
+
let reported = false;
|
|
26046
|
+
const report = (outcome) => {
|
|
26047
|
+
if (reported || !data.linkId) return;
|
|
26048
|
+
reported = true;
|
|
26049
|
+
socket.emit("runnerLinkReport", {
|
|
26050
|
+
linkId: data.linkId,
|
|
26051
|
+
peerSocketId: data.fromSocketId,
|
|
26052
|
+
outcome,
|
|
26053
|
+
detail: [
|
|
26054
|
+
`ice=${view.ice} gathering=${view.gathering} relays=${view.relays}`,
|
|
26055
|
+
`local=${[...view.local].join(",") || "none"}`,
|
|
26056
|
+
`remote=${[...view.remote].join(",") || "none"}`,
|
|
26057
|
+
// Hostname and port only — the credential never leaves the runner.
|
|
26058
|
+
`turn=${iceServers.map(
|
|
26059
|
+
(u) => typeof u === "string" ? u : `${u.relayType}:${u.hostname}:${u.port}`
|
|
26060
|
+
).join(" ")}`
|
|
26061
|
+
].join("\n")
|
|
26062
|
+
});
|
|
26063
|
+
};
|
|
24154
26064
|
const iceCandidateHandler = (msg) => {
|
|
24155
26065
|
if (msg.fromSocketId === data.fromSocketId) {
|
|
24156
26066
|
console.log(`[webrtc] remote ICE candidate: ${msg.candidate.candidate.slice(0, 60)}...`);
|
|
26067
|
+
view.remote.add(candidateType(msg.candidate.candidate));
|
|
24157
26068
|
peer.addRemoteCandidate(
|
|
24158
26069
|
msg.candidate.candidate,
|
|
24159
26070
|
msg.candidate.sdpMid || ""
|
|
@@ -24169,18 +26080,31 @@ async function startDaemon(config) {
|
|
|
24169
26080
|
};
|
|
24170
26081
|
peer.onStateChange((state) => {
|
|
24171
26082
|
console.log(`[webrtc] peer state: ${state}`);
|
|
26083
|
+
view.ice = state;
|
|
26084
|
+
if (state === "connected") report("connected");
|
|
24172
26085
|
if (state === "failed" || state === "closed" || state === "disconnected") {
|
|
26086
|
+
if (state === "failed") report("failed");
|
|
24173
26087
|
detachIceHandler();
|
|
24174
26088
|
}
|
|
24175
26089
|
});
|
|
24176
26090
|
peer.onGatheringStateChange((state) => {
|
|
24177
26091
|
console.log(`[webrtc] gathering state: ${state}`);
|
|
26092
|
+
view.gathering = state;
|
|
24178
26093
|
});
|
|
24179
26094
|
peer.onLocalCandidate((candidate, mid) => {
|
|
24180
26095
|
console.log(`[webrtc] local ICE candidate: ${candidate.slice(0, 60)}...`);
|
|
26096
|
+
const type = candidateType(candidate);
|
|
26097
|
+
view.local.add(type);
|
|
26098
|
+
if (type === "relay") view.relays += 1;
|
|
24181
26099
|
socket.emit("runnerIceCandidate", {
|
|
24182
26100
|
targetSocketId: data.fromSocketId,
|
|
24183
|
-
|
|
26101
|
+
// 0 rather than null. A browser accepts a null index, but a mobile
|
|
26102
|
+
// client on the New Architecture passes this straight into codegen'd
|
|
26103
|
+
// JSI bindings that type-check it as a number, and null throws there —
|
|
26104
|
+
// rejecting every candidate we send and stranding the link at
|
|
26105
|
+
// checking. sdpMid identifies the m-line on its own, so the index is
|
|
26106
|
+
// redundant here anyway.
|
|
26107
|
+
candidate: { candidate, sdpMid: mid, sdpMLineIndex: 0 }
|
|
24184
26108
|
});
|
|
24185
26109
|
});
|
|
24186
26110
|
peer.onLocalDescription((sdp, _type) => {
|