opencode-skills-deck 0.2.0-rc.0 → 0.3.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/README.md +70 -56
- package/dist/gui-experimental.cjs +61 -0
- package/dist/index.js +1455 -118
- package/dist/index.js.map +4 -4
- package/dist/rpc.js +19929 -0
- package/dist/rpc.js.map +7 -0
- package/dist/tui.js +151 -37
- package/dist/tui.js.map +3 -3
- package/package.json +27 -7
package/dist/index.js
CHANGED
|
@@ -5,8 +5,13 @@ var __export = (target, all) => {
|
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
// packages/plugin/src/index.ts
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
8
9
|
import { Plugin } from "@opencode/plugin";
|
|
9
10
|
|
|
11
|
+
// packages/core/src/config.ts
|
|
12
|
+
import { readFileSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
|
|
10
15
|
// node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/classic/external.js
|
|
11
16
|
var external_exports = {};
|
|
12
17
|
__export(external_exports, {
|
|
@@ -3621,8 +3626,8 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) =>
|
|
|
3621
3626
|
const _normalized = cached(() => normalizeDef(def));
|
|
3622
3627
|
const memo2 = globalConfig.memoizer;
|
|
3623
3628
|
const generateFastpass = (shape) => {
|
|
3624
|
-
const
|
|
3625
|
-
const syms =
|
|
3629
|
+
const normalized2 = _normalized.value;
|
|
3630
|
+
const syms = normalized2.symbolKeys;
|
|
3626
3631
|
const doc = new Doc(["payload", "ctx"], { shape, inst, memo: memo2, syms });
|
|
3627
3632
|
const parseStr = (k) => `shape[${k}]._zod.run({ value: input[${k}], issues: [] }, ctx)`;
|
|
3628
3633
|
const prefixStr = (id, k) => `
|
|
@@ -3640,11 +3645,11 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) =>
|
|
|
3640
3645
|
doc.write(`const input = payload.value;`);
|
|
3641
3646
|
const ids = /* @__PURE__ */ Object.create(null);
|
|
3642
3647
|
let counter = 0;
|
|
3643
|
-
for (const key of
|
|
3648
|
+
for (const key of normalized2.allKeys) {
|
|
3644
3649
|
ids[key] = `key_${counter++}`;
|
|
3645
3650
|
}
|
|
3646
3651
|
doc.write(memo2 ? `const newResult = memo.alloc(inst, payload, {}, ctx);` : `const newResult = {};`);
|
|
3647
|
-
for (const key of
|
|
3652
|
+
for (const key of normalized2.allKeys) {
|
|
3648
3653
|
if (key === "__proto__")
|
|
3649
3654
|
continue;
|
|
3650
3655
|
const id = ids[key];
|
|
@@ -5074,7 +5079,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
|
|
|
5074
5079
|
var NONE = 0;
|
|
5075
5080
|
var ASSUMED = 1;
|
|
5076
5081
|
var PROVEN = 2;
|
|
5077
|
-
function isRecursive(inst, stack,
|
|
5082
|
+
function isRecursive(inst, stack, resolve3) {
|
|
5078
5083
|
const cached2 = recursive.get(inst);
|
|
5079
5084
|
if (cached2 !== void 0)
|
|
5080
5085
|
return cached2 ? PROVEN : NONE;
|
|
@@ -5084,7 +5089,7 @@ function isRecursive(inst, stack, resolve) {
|
|
|
5084
5089
|
let result = NONE;
|
|
5085
5090
|
const check2 = (child) => {
|
|
5086
5091
|
if (result !== PROVEN && child?._zod) {
|
|
5087
|
-
const answer = isRecursive(child, stack,
|
|
5092
|
+
const answer = isRecursive(child, stack, resolve3);
|
|
5088
5093
|
if (answer > result)
|
|
5089
5094
|
result = answer;
|
|
5090
5095
|
}
|
|
@@ -5095,7 +5100,7 @@ function isRecursive(inst, stack, resolve) {
|
|
|
5095
5100
|
const desc = Object.getOwnPropertyDescriptor(sh, key);
|
|
5096
5101
|
if (spread && !desc.enumerable)
|
|
5097
5102
|
continue;
|
|
5098
|
-
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack,
|
|
5103
|
+
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve3) : NONE;
|
|
5099
5104
|
if (child > answer)
|
|
5100
5105
|
answer = child;
|
|
5101
5106
|
}
|
|
@@ -5159,7 +5164,7 @@ function isRecursive(inst, stack, resolve) {
|
|
|
5159
5164
|
break;
|
|
5160
5165
|
// `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
|
|
5161
5166
|
case "lazy": {
|
|
5162
|
-
const inner = def._cachedInner ?? (
|
|
5167
|
+
const inner = def._cachedInner ?? (resolve3 ? inst._zod.innerType : void 0);
|
|
5163
5168
|
merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
|
|
5164
5169
|
break;
|
|
5165
5170
|
}
|
|
@@ -19483,23 +19488,23 @@ function fromJSONSchema(schema, params) {
|
|
|
19483
19488
|
if (typeof schema === "boolean") {
|
|
19484
19489
|
return schema ? z.any() : z.never();
|
|
19485
19490
|
}
|
|
19486
|
-
let
|
|
19491
|
+
let normalized2;
|
|
19487
19492
|
try {
|
|
19488
|
-
|
|
19493
|
+
normalized2 = JSON.parse(JSON.stringify(schema));
|
|
19489
19494
|
} catch {
|
|
19490
19495
|
throw new Error("fromJSONSchema input is not valid JSON (possibly cyclic); use $defs/$ref for recursive schemas");
|
|
19491
19496
|
}
|
|
19492
|
-
const version2 = detectVersion(
|
|
19493
|
-
const defs =
|
|
19497
|
+
const version2 = detectVersion(normalized2, params?.defaultTarget);
|
|
19498
|
+
const defs = normalized2.$defs || normalized2.definitions || {};
|
|
19494
19499
|
const ctx = {
|
|
19495
19500
|
version: version2,
|
|
19496
19501
|
defs,
|
|
19497
19502
|
refs: /* @__PURE__ */ new Map(),
|
|
19498
19503
|
processing: /* @__PURE__ */ new Set(),
|
|
19499
|
-
rootSchema:
|
|
19504
|
+
rootSchema: normalized2,
|
|
19500
19505
|
registry: params?.registry ?? globalRegistry
|
|
19501
19506
|
};
|
|
19502
|
-
return convertSchema(
|
|
19507
|
+
return convertSchema(normalized2, ctx);
|
|
19503
19508
|
}
|
|
19504
19509
|
|
|
19505
19510
|
// node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/core/visit.js
|
|
@@ -19745,25 +19750,121 @@ var configSchema = external_exports.object({
|
|
|
19745
19750
|
repos: external_exports.array(repoSchema).default([])
|
|
19746
19751
|
});
|
|
19747
19752
|
var defaultConfig = configSchema.parse({});
|
|
19753
|
+
function configFilePath(paths) {
|
|
19754
|
+
return join(paths.configDir, "config.json");
|
|
19755
|
+
}
|
|
19756
|
+
function parseConfig(raw) {
|
|
19757
|
+
return configSchema.parse(raw);
|
|
19758
|
+
}
|
|
19759
|
+
function loadConfig(paths) {
|
|
19760
|
+
try {
|
|
19761
|
+
const text = readFileSync(configFilePath(paths), "utf8");
|
|
19762
|
+
return parseConfig(JSON.parse(text));
|
|
19763
|
+
} catch {
|
|
19764
|
+
return defaultConfig;
|
|
19765
|
+
}
|
|
19766
|
+
}
|
|
19767
|
+
function fillTemplate(template, values) {
|
|
19768
|
+
return template.replace(/\{(\w+)\}/g, (match, key) => {
|
|
19769
|
+
const value = values[key];
|
|
19770
|
+
return value === void 0 ? match : String(value);
|
|
19771
|
+
});
|
|
19772
|
+
}
|
|
19748
19773
|
|
|
19749
19774
|
// packages/core/src/exec.ts
|
|
19775
|
+
import { spawn } from "node:child_process";
|
|
19750
19776
|
var MAX_BUFFER = 16 * 1024 * 1024;
|
|
19777
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
19778
|
+
function createRunner(options = {}) {
|
|
19779
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
19780
|
+
return (command, args, options2) => new Promise((resolve3, reject) => {
|
|
19781
|
+
const child = spawn(command, args, {
|
|
19782
|
+
cwd: options2?.cwd,
|
|
19783
|
+
env: process.env,
|
|
19784
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
19785
|
+
});
|
|
19786
|
+
let stdout = "";
|
|
19787
|
+
let stderr = "";
|
|
19788
|
+
let overflow = false;
|
|
19789
|
+
let settled = false;
|
|
19790
|
+
const fail = (error62) => {
|
|
19791
|
+
if (settled) return;
|
|
19792
|
+
settled = true;
|
|
19793
|
+
clearTimeout(timer);
|
|
19794
|
+
reject(error62);
|
|
19795
|
+
};
|
|
19796
|
+
const succeed = (result) => {
|
|
19797
|
+
if (settled) return;
|
|
19798
|
+
settled = true;
|
|
19799
|
+
clearTimeout(timer);
|
|
19800
|
+
resolve3(result);
|
|
19801
|
+
};
|
|
19802
|
+
const timer = setTimeout(() => {
|
|
19803
|
+
child.kill("SIGTERM");
|
|
19804
|
+
fail(new Error(`${command} ${args.join(" ")} timed out after ${timeoutMs}ms`));
|
|
19805
|
+
}, timeoutMs);
|
|
19806
|
+
timer.unref?.();
|
|
19807
|
+
child.stdout?.setEncoding("utf8");
|
|
19808
|
+
child.stderr?.setEncoding("utf8");
|
|
19809
|
+
child.stdout?.on("data", (chunk) => {
|
|
19810
|
+
if (overflow) return;
|
|
19811
|
+
stdout += chunk;
|
|
19812
|
+
if (stdout.length > MAX_BUFFER) {
|
|
19813
|
+
overflow = true;
|
|
19814
|
+
child.kill();
|
|
19815
|
+
}
|
|
19816
|
+
});
|
|
19817
|
+
child.stderr?.on("data", (chunk) => {
|
|
19818
|
+
if (overflow) return;
|
|
19819
|
+
stderr += chunk;
|
|
19820
|
+
if (stderr.length > MAX_BUFFER) {
|
|
19821
|
+
overflow = true;
|
|
19822
|
+
child.kill();
|
|
19823
|
+
}
|
|
19824
|
+
});
|
|
19825
|
+
child.on("error", (error62) => {
|
|
19826
|
+
fail(new Error(`${command} ${args.join(" ")} failed: ${error62.message}`));
|
|
19827
|
+
});
|
|
19828
|
+
child.on("close", (code) => {
|
|
19829
|
+
if (settled) return;
|
|
19830
|
+
if (overflow) {
|
|
19831
|
+
fail(new Error(`${command} ${args.join(" ")} failed: \u51FA\u529B\u304C\u5927\u304D\u3059\u304E\u307E\u3059`));
|
|
19832
|
+
return;
|
|
19833
|
+
}
|
|
19834
|
+
if (code === 0) {
|
|
19835
|
+
succeed({ stdout, stderr });
|
|
19836
|
+
return;
|
|
19837
|
+
}
|
|
19838
|
+
const detail = stderr.trim() || `exit code ${code ?? "unknown"}`;
|
|
19839
|
+
fail(new Error(`${command} ${args.join(" ")} failed: ${detail}`));
|
|
19840
|
+
});
|
|
19841
|
+
});
|
|
19842
|
+
}
|
|
19751
19843
|
|
|
19752
19844
|
// packages/core/src/paths.ts
|
|
19753
19845
|
import { homedir } from "node:os";
|
|
19754
|
-
import { join } from "node:path";
|
|
19846
|
+
import { join as join2 } from "node:path";
|
|
19755
19847
|
var APP_DIR = "opencode-skills-deck";
|
|
19756
19848
|
function configRootOf(platform, env, home) {
|
|
19757
|
-
return env.XDG_CONFIG_HOME ?? (platform === "win32" ? env.APPDATA ??
|
|
19849
|
+
return env.XDG_CONFIG_HOME ?? (platform === "win32" ? env.APPDATA ?? join2(home, "AppData", "Roaming") : join2(home, ".config"));
|
|
19758
19850
|
}
|
|
19759
19851
|
function stateRootOf(platform, env, home) {
|
|
19760
|
-
return env.XDG_STATE_HOME ?? (platform === "win32" ? env.LOCALAPPDATA ??
|
|
19852
|
+
return env.XDG_STATE_HOME ?? (platform === "win32" ? env.LOCALAPPDATA ?? join2(home, "AppData", "Local") : join2(home, ".local", "state"));
|
|
19853
|
+
}
|
|
19854
|
+
function opencodeConfigDir(options = {}) {
|
|
19855
|
+
const platform = options.platform ?? process.platform;
|
|
19856
|
+
const env = options.env ?? process.env;
|
|
19857
|
+
const home = options.home ?? homedir();
|
|
19858
|
+
return join2(configRootOf(platform, env, home), "opencode");
|
|
19859
|
+
}
|
|
19860
|
+
function opencodeSkillsDir(options = {}) {
|
|
19861
|
+
return join2(opencodeConfigDir(options), "skills");
|
|
19761
19862
|
}
|
|
19762
19863
|
function repoSessionSlug(repo) {
|
|
19763
19864
|
return repo.replace(/[^a-zA-Z0-9._-]+/g, "__");
|
|
19764
19865
|
}
|
|
19765
19866
|
function sessionsDirFor(paths, repo) {
|
|
19766
|
-
return repo ?
|
|
19867
|
+
return repo ? join2(paths.sessionsDir, repoSessionSlug(repo)) : paths.sessionsDir;
|
|
19767
19868
|
}
|
|
19768
19869
|
function resolvePaths(options = {}) {
|
|
19769
19870
|
const platform = options.platform ?? process.platform;
|
|
@@ -19771,34 +19872,155 @@ function resolvePaths(options = {}) {
|
|
|
19771
19872
|
const home = options.home ?? homedir();
|
|
19772
19873
|
const configRoot = configRootOf(platform, env, home);
|
|
19773
19874
|
const stateRoot = stateRootOf(platform, env, home);
|
|
19774
|
-
const configDir =
|
|
19775
|
-
const stateDir =
|
|
19875
|
+
const configDir = join2(configRoot, APP_DIR);
|
|
19876
|
+
const stateDir = join2(stateRoot, APP_DIR);
|
|
19776
19877
|
return {
|
|
19777
19878
|
configDir,
|
|
19778
19879
|
stateDir,
|
|
19779
|
-
sessionsDir:
|
|
19780
|
-
issueCacheDir:
|
|
19880
|
+
sessionsDir: join2(stateDir, "sessions"),
|
|
19881
|
+
issueCacheDir: join2(stateDir, "issues")
|
|
19781
19882
|
};
|
|
19782
19883
|
}
|
|
19783
19884
|
|
|
19885
|
+
// packages/core/src/repo.ts
|
|
19886
|
+
import { dirname, join as join3, resolve } from "node:path";
|
|
19887
|
+
var REMOTE_PATTERNS = [
|
|
19888
|
+
/^git@[^:]+:(?<owner>[^/]+)\/(?<name>[^/]+?)(?:\.git)?$/,
|
|
19889
|
+
/^(?:ssh|git|https?):\/\/(?:[^@/]+@)?[^/]+\/(?<owner>[^/]+)\/(?<name>[^/]+?)(?:\.git)?\/?$/
|
|
19890
|
+
];
|
|
19891
|
+
function parseRemoteUrl(url2) {
|
|
19892
|
+
const trimmed = url2.trim();
|
|
19893
|
+
for (const pattern of REMOTE_PATTERNS) {
|
|
19894
|
+
const match = pattern.exec(trimmed);
|
|
19895
|
+
const owner = match?.groups?.owner;
|
|
19896
|
+
const name = match?.groups?.name;
|
|
19897
|
+
if (owner && name) return { owner, name };
|
|
19898
|
+
}
|
|
19899
|
+
return null;
|
|
19900
|
+
}
|
|
19901
|
+
function slugify2(input2, maxLength = 40) {
|
|
19902
|
+
const base = input2.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
19903
|
+
if (!base) return "";
|
|
19904
|
+
return base.slice(0, maxLength).replace(/-+$/g, "");
|
|
19905
|
+
}
|
|
19906
|
+
function branchName(issueNumber, title) {
|
|
19907
|
+
const slug = slugify2(title);
|
|
19908
|
+
return slug ? `issue/${issueNumber}-${slug}` : `issue/${issueNumber}`;
|
|
19909
|
+
}
|
|
19910
|
+
function issueWorktreePath(repoRoot, issueNumber) {
|
|
19911
|
+
return join3(repoRoot, ".worktrees", `issue-${issueNumber}`);
|
|
19912
|
+
}
|
|
19913
|
+
async function resolveRepoRoot(directory, runner) {
|
|
19914
|
+
let worktreeRoot;
|
|
19915
|
+
try {
|
|
19916
|
+
const { stdout } = await runner("git", ["rev-parse", "--show-toplevel"], { cwd: directory });
|
|
19917
|
+
worktreeRoot = stdout.trim();
|
|
19918
|
+
if (!worktreeRoot) return null;
|
|
19919
|
+
} catch {
|
|
19920
|
+
return null;
|
|
19921
|
+
}
|
|
19922
|
+
try {
|
|
19923
|
+
const { stdout } = await runner("git", ["rev-parse", "--path-format=absolute", "--git-common-dir"], {
|
|
19924
|
+
cwd: worktreeRoot
|
|
19925
|
+
});
|
|
19926
|
+
const common = stdout.trim();
|
|
19927
|
+
if (common.endsWith(".git")) return dirname(common);
|
|
19928
|
+
} catch {
|
|
19929
|
+
try {
|
|
19930
|
+
const { stdout } = await runner("git", ["rev-parse", "--git-common-dir"], {
|
|
19931
|
+
cwd: worktreeRoot
|
|
19932
|
+
});
|
|
19933
|
+
const common = resolve(worktreeRoot, stdout.trim());
|
|
19934
|
+
if (common.endsWith(".git")) return dirname(common);
|
|
19935
|
+
} catch {
|
|
19936
|
+
}
|
|
19937
|
+
}
|
|
19938
|
+
return worktreeRoot;
|
|
19939
|
+
}
|
|
19940
|
+
async function readRepoInfo(directory, runner) {
|
|
19941
|
+
const root = await resolveRepoRoot(directory, runner);
|
|
19942
|
+
if (!root) return null;
|
|
19943
|
+
let remote;
|
|
19944
|
+
try {
|
|
19945
|
+
const { stdout } = await runner("git", ["remote", "get-url", "origin"], { cwd: root });
|
|
19946
|
+
remote = stdout.trim();
|
|
19947
|
+
} catch {
|
|
19948
|
+
return null;
|
|
19949
|
+
}
|
|
19950
|
+
const ref = parseRemoteUrl(remote);
|
|
19951
|
+
if (!ref) return null;
|
|
19952
|
+
let defaultBranch2 = "main";
|
|
19953
|
+
try {
|
|
19954
|
+
const { stdout } = await runner("git", ["symbolic-ref", "--quiet", "refs/remotes/origin/HEAD"], {
|
|
19955
|
+
cwd: root
|
|
19956
|
+
});
|
|
19957
|
+
const value = stdout.trim().replace(/^refs\/remotes\/origin\//, "");
|
|
19958
|
+
if (value) defaultBranch2 = value;
|
|
19959
|
+
} catch {
|
|
19960
|
+
}
|
|
19961
|
+
let branch = defaultBranch2;
|
|
19962
|
+
try {
|
|
19963
|
+
const { stdout } = await runner("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: root });
|
|
19964
|
+
const current = stdout.trim();
|
|
19965
|
+
if (current) branch = current;
|
|
19966
|
+
} catch {
|
|
19967
|
+
}
|
|
19968
|
+
return { root, ref, defaultBranch: defaultBranch2, branch };
|
|
19969
|
+
}
|
|
19970
|
+
async function resolveRepoContexts(input2) {
|
|
19971
|
+
const contexts = [];
|
|
19972
|
+
if (input2.config.repos.length === 0) {
|
|
19973
|
+
const info = await readRepoInfo(input2.directory, input2.runner);
|
|
19974
|
+
if (info) {
|
|
19975
|
+
contexts.push({
|
|
19976
|
+
repo: `${info.ref.owner}/${info.ref.name}`,
|
|
19977
|
+
root: info.root,
|
|
19978
|
+
info,
|
|
19979
|
+
configured: false
|
|
19980
|
+
});
|
|
19981
|
+
}
|
|
19982
|
+
return contexts;
|
|
19983
|
+
}
|
|
19984
|
+
for (const entry of input2.config.repos) {
|
|
19985
|
+
const root = entry.root ?? input2.directory;
|
|
19986
|
+
const info = await readRepoInfo(root, input2.runner);
|
|
19987
|
+
if (info) {
|
|
19988
|
+
contexts.push({
|
|
19989
|
+
repo: `${info.ref.owner}/${info.ref.name}`,
|
|
19990
|
+
root: info.root,
|
|
19991
|
+
info,
|
|
19992
|
+
configured: true
|
|
19993
|
+
});
|
|
19994
|
+
continue;
|
|
19995
|
+
}
|
|
19996
|
+
contexts.push({ repo: entry.repo, root, info: null, configured: true });
|
|
19997
|
+
}
|
|
19998
|
+
const seen = /* @__PURE__ */ new Set();
|
|
19999
|
+
return contexts.filter((context) => {
|
|
20000
|
+
if (seen.has(context.repo)) return false;
|
|
20001
|
+
seen.add(context.repo);
|
|
20002
|
+
return true;
|
|
20003
|
+
});
|
|
20004
|
+
}
|
|
20005
|
+
|
|
19784
20006
|
// packages/core/src/state.ts
|
|
19785
20007
|
import {
|
|
19786
20008
|
existsSync,
|
|
19787
20009
|
mkdirSync,
|
|
19788
20010
|
readdirSync,
|
|
19789
|
-
readFileSync,
|
|
20011
|
+
readFileSync as readFileSync2,
|
|
19790
20012
|
renameSync,
|
|
19791
20013
|
rmSync,
|
|
19792
20014
|
watch,
|
|
19793
20015
|
writeFileSync
|
|
19794
20016
|
} from "node:fs";
|
|
19795
|
-
import { join as
|
|
20017
|
+
import { join as join4 } from "node:path";
|
|
19796
20018
|
function sessionFilePath(dir, key) {
|
|
19797
|
-
return
|
|
20019
|
+
return join4(dir, `${key}.json`);
|
|
19798
20020
|
}
|
|
19799
20021
|
function readSession(dir, key) {
|
|
19800
20022
|
try {
|
|
19801
|
-
const parsed = JSON.parse(
|
|
20023
|
+
const parsed = JSON.parse(readFileSync2(sessionFilePath(dir, key), "utf8"));
|
|
19802
20024
|
return typeof parsed?.key === "string" && typeof parsed?.status === "string" ? parsed : null;
|
|
19803
20025
|
} catch {
|
|
19804
20026
|
return null;
|
|
@@ -19815,9 +20037,9 @@ function writeSession(dir, record2) {
|
|
|
19815
20037
|
var PREVIEW_MAX_LINES = 3;
|
|
19816
20038
|
var PREVIEW_MAX_CHARS = 500;
|
|
19817
20039
|
function truncatePreview(text, lines = PREVIEW_MAX_LINES, chars = PREVIEW_MAX_CHARS) {
|
|
19818
|
-
const
|
|
19819
|
-
if (!
|
|
19820
|
-
const tail =
|
|
20040
|
+
const normalized2 = text.replace(/\r\n?/g, "\n").split(String.fromCharCode(0)).join("").trimEnd();
|
|
20041
|
+
if (!normalized2) return "";
|
|
20042
|
+
const tail = normalized2.split("\n").slice(-lines).join("\n");
|
|
19821
20043
|
return tail.length > chars ? tail.slice(tail.length - chars) : tail;
|
|
19822
20044
|
}
|
|
19823
20045
|
function appendPreview(current, chunk) {
|
|
@@ -19825,10 +20047,1077 @@ function appendPreview(current, chunk) {
|
|
|
19825
20047
|
}
|
|
19826
20048
|
var SESSION_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
19827
20049
|
|
|
20050
|
+
// packages/core/src/worktree.ts
|
|
20051
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, realpathSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
20052
|
+
import { join as join5, resolve as resolve2, sep } from "node:path";
|
|
20053
|
+
async function defaultBranch(input2) {
|
|
20054
|
+
if (input2.configured) return input2.configured;
|
|
20055
|
+
try {
|
|
20056
|
+
const { stdout } = await input2.runner("git", ["symbolic-ref", "--quiet", "refs/remotes/origin/HEAD"], {
|
|
20057
|
+
cwd: input2.repoRoot
|
|
20058
|
+
});
|
|
20059
|
+
const value = stdout.trim().replace(/^refs\/remotes\/origin\//, "");
|
|
20060
|
+
if (value) return value;
|
|
20061
|
+
} catch {
|
|
20062
|
+
}
|
|
20063
|
+
return "main";
|
|
20064
|
+
}
|
|
20065
|
+
async function ensureWorktreesExcluded(input2) {
|
|
20066
|
+
const { stdout } = await input2.runner("git", ["rev-parse", "--git-path", "info/exclude"], {
|
|
20067
|
+
cwd: input2.repoRoot
|
|
20068
|
+
});
|
|
20069
|
+
const path = join5(input2.repoRoot, stdout.trim());
|
|
20070
|
+
const existing = existsSync2(path) ? readFileSync3(path, "utf8") : "";
|
|
20071
|
+
if (existing.split("\n").some((line) => line.trim() === ".worktrees/")) return;
|
|
20072
|
+
const suffix = existing.endsWith("\n") || existing === "" ? "" : "\n";
|
|
20073
|
+
writeFileSync2(path, `${existing}${suffix}.worktrees/
|
|
20074
|
+
`);
|
|
20075
|
+
}
|
|
20076
|
+
async function readWorktreeEntries(input2) {
|
|
20077
|
+
const { stdout } = await input2.runner("git", ["worktree", "list", "--porcelain"], {
|
|
20078
|
+
cwd: input2.repoRoot
|
|
20079
|
+
});
|
|
20080
|
+
const entries = [];
|
|
20081
|
+
let current = null;
|
|
20082
|
+
for (const line of stdout.split("\n")) {
|
|
20083
|
+
if (line.startsWith("worktree ")) {
|
|
20084
|
+
if (current) entries.push(current);
|
|
20085
|
+
current = {
|
|
20086
|
+
directory: line.slice("worktree ".length).trim(),
|
|
20087
|
+
branch: null,
|
|
20088
|
+
detached: false,
|
|
20089
|
+
bare: false
|
|
20090
|
+
};
|
|
20091
|
+
continue;
|
|
20092
|
+
}
|
|
20093
|
+
if (!current) continue;
|
|
20094
|
+
if (line.startsWith("branch ")) {
|
|
20095
|
+
current.branch = line.slice("branch ".length).replace(/^refs\/heads\//, "").trim();
|
|
20096
|
+
} else if (line === "detached") {
|
|
20097
|
+
current.detached = true;
|
|
20098
|
+
} else if (line === "bare") {
|
|
20099
|
+
current.bare = true;
|
|
20100
|
+
}
|
|
20101
|
+
}
|
|
20102
|
+
if (current) entries.push(current);
|
|
20103
|
+
return entries;
|
|
20104
|
+
}
|
|
20105
|
+
async function registeredWorktrees(input2) {
|
|
20106
|
+
return (await readWorktreeEntries(input2)).map((entry) => entry.directory);
|
|
20107
|
+
}
|
|
20108
|
+
function normalized(path) {
|
|
20109
|
+
try {
|
|
20110
|
+
return realpathSync(path).replace(/\/+$/, "");
|
|
20111
|
+
} catch {
|
|
20112
|
+
return resolve2(path).replace(/\/+$/, "");
|
|
20113
|
+
}
|
|
20114
|
+
}
|
|
20115
|
+
async function branchExists(input2) {
|
|
20116
|
+
try {
|
|
20117
|
+
await input2.runner("git", ["show-ref", "--verify", "--quiet", `refs/heads/${input2.branch}`], {
|
|
20118
|
+
cwd: input2.repoRoot
|
|
20119
|
+
});
|
|
20120
|
+
return true;
|
|
20121
|
+
} catch {
|
|
20122
|
+
return false;
|
|
20123
|
+
}
|
|
20124
|
+
}
|
|
20125
|
+
async function ensureWorktree(input2) {
|
|
20126
|
+
const directory = issueWorktreePath(input2.repoRoot, input2.issueNumber);
|
|
20127
|
+
const branch = branchName(input2.issueNumber, input2.title);
|
|
20128
|
+
const base = await defaultBranch({
|
|
20129
|
+
repoRoot: input2.repoRoot,
|
|
20130
|
+
runner: input2.runner,
|
|
20131
|
+
configured: input2.baseBranch
|
|
20132
|
+
});
|
|
20133
|
+
await ensureWorktreesExcluded({ repoRoot: input2.repoRoot, runner: input2.runner });
|
|
20134
|
+
const registered = await registeredWorktrees({ repoRoot: input2.repoRoot, runner: input2.runner });
|
|
20135
|
+
if (registered.includes(directory)) return { directory, branch, base, created: false };
|
|
20136
|
+
if (await branchExists({ repoRoot: input2.repoRoot, runner: input2.runner, branch })) {
|
|
20137
|
+
await input2.runner("git", ["worktree", "add", directory, branch], { cwd: input2.repoRoot });
|
|
20138
|
+
return { directory, branch, base, created: true };
|
|
20139
|
+
}
|
|
20140
|
+
try {
|
|
20141
|
+
await input2.runner("git", ["fetch", "--quiet", "origin", base], { cwd: input2.repoRoot });
|
|
20142
|
+
} catch {
|
|
20143
|
+
}
|
|
20144
|
+
try {
|
|
20145
|
+
await input2.runner("git", ["worktree", "add", "-b", branch, directory, `origin/${base}`], {
|
|
20146
|
+
cwd: input2.repoRoot
|
|
20147
|
+
});
|
|
20148
|
+
} catch {
|
|
20149
|
+
await input2.runner("git", ["worktree", "add", "-b", branch, directory, base], { cwd: input2.repoRoot });
|
|
20150
|
+
}
|
|
20151
|
+
return { directory, branch, base, created: true };
|
|
20152
|
+
}
|
|
20153
|
+
async function removeWorktree(input2) {
|
|
20154
|
+
const registered = await registeredWorktrees({ repoRoot: input2.repoRoot, runner: input2.runner });
|
|
20155
|
+
if (!registered.includes(input2.directory)) return { removed: false, reason: "missing" };
|
|
20156
|
+
const { stdout } = await input2.runner("git", ["status", "--porcelain"], { cwd: input2.directory });
|
|
20157
|
+
if (stdout.trim() !== "") return { removed: false, reason: "dirty" };
|
|
20158
|
+
await input2.runner("git", ["worktree", "remove", input2.directory], { cwd: input2.repoRoot });
|
|
20159
|
+
return { removed: true };
|
|
20160
|
+
}
|
|
20161
|
+
function worktreeIssueNumber(directory) {
|
|
20162
|
+
const name = normalized(directory).split("/").at(-1) ?? "";
|
|
20163
|
+
const match = /^issue-(\d+)$/.exec(name);
|
|
20164
|
+
if (!match) return null;
|
|
20165
|
+
const value = Number(match[1]);
|
|
20166
|
+
return Number.isSafeInteger(value) && value > 0 ? value : null;
|
|
20167
|
+
}
|
|
20168
|
+
function isInsideWorktrees(repoRoot, directory) {
|
|
20169
|
+
const root = normalized(join5(repoRoot, ".worktrees"));
|
|
20170
|
+
return normalized(directory).startsWith(`${root}${sep}`);
|
|
20171
|
+
}
|
|
20172
|
+
async function readWorktreeStatus(input2) {
|
|
20173
|
+
const entries = await readWorktreeEntries(input2);
|
|
20174
|
+
const entry = entries.find((candidate) => normalized(candidate.directory) === normalized(input2.directory));
|
|
20175
|
+
const exists = existsSync2(input2.directory);
|
|
20176
|
+
let dirty = false;
|
|
20177
|
+
if (exists) {
|
|
20178
|
+
try {
|
|
20179
|
+
const { stdout } = await input2.runner("git", ["status", "--porcelain"], {
|
|
20180
|
+
cwd: input2.directory
|
|
20181
|
+
});
|
|
20182
|
+
dirty = stdout.trim() !== "";
|
|
20183
|
+
} catch {
|
|
20184
|
+
dirty = true;
|
|
20185
|
+
}
|
|
20186
|
+
}
|
|
20187
|
+
return {
|
|
20188
|
+
directory: input2.directory,
|
|
20189
|
+
branch: entry?.branch ?? null,
|
|
20190
|
+
registered: Boolean(entry),
|
|
20191
|
+
exists,
|
|
20192
|
+
dirty,
|
|
20193
|
+
issueNumber: worktreeIssueNumber(input2.directory)
|
|
20194
|
+
};
|
|
20195
|
+
}
|
|
20196
|
+
async function listIssueWorktrees(input2) {
|
|
20197
|
+
const entries = await readWorktreeEntries(input2);
|
|
20198
|
+
const result = [];
|
|
20199
|
+
for (const entry of entries) {
|
|
20200
|
+
if (!isInsideWorktrees(input2.repoRoot, entry.directory)) continue;
|
|
20201
|
+
const issueNumber = worktreeIssueNumber(entry.directory);
|
|
20202
|
+
if (issueNumber === null) continue;
|
|
20203
|
+
let dirty = false;
|
|
20204
|
+
try {
|
|
20205
|
+
const { stdout } = await input2.runner("git", ["status", "--porcelain"], {
|
|
20206
|
+
cwd: entry.directory
|
|
20207
|
+
});
|
|
20208
|
+
dirty = stdout.trim() !== "";
|
|
20209
|
+
} catch {
|
|
20210
|
+
dirty = true;
|
|
20211
|
+
}
|
|
20212
|
+
result.push({
|
|
20213
|
+
directory: entry.directory,
|
|
20214
|
+
branch: entry.branch,
|
|
20215
|
+
registered: true,
|
|
20216
|
+
exists: existsSync2(entry.directory),
|
|
20217
|
+
dirty,
|
|
20218
|
+
issueNumber
|
|
20219
|
+
});
|
|
20220
|
+
}
|
|
20221
|
+
return result;
|
|
20222
|
+
}
|
|
20223
|
+
function selectOrphanWorktrees(worktrees, openIssueNumbers) {
|
|
20224
|
+
return worktrees.filter(
|
|
20225
|
+
(worktree) => worktree.issueNumber === null || !openIssueNumbers.has(worktree.issueNumber)
|
|
20226
|
+
);
|
|
20227
|
+
}
|
|
20228
|
+
|
|
20229
|
+
// packages/core/src/issue.ts
|
|
20230
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
20231
|
+
import { dirname as dirname2, join as join6 } from "node:path";
|
|
20232
|
+
var SUMMARY_HEADING = /summary|要約|概要/i;
|
|
20233
|
+
var CRITERIA_HEADING = /acceptance criteria|受け入れ基準|完了条件|acceptance/i;
|
|
20234
|
+
var BULLET = /^\s*(?:[-*+]|\d+\.)\s+(?:\[[ xX]\]\s*)?(.+)$/;
|
|
20235
|
+
function parseIssueBody(body) {
|
|
20236
|
+
const sections = [{ title: "", lines: [] }];
|
|
20237
|
+
for (const line of body.replace(/\r\n?/g, "\n").split("\n")) {
|
|
20238
|
+
const heading = /^##\s+(.+?)\s*$/.exec(line);
|
|
20239
|
+
if (heading) {
|
|
20240
|
+
sections.push({ title: heading[1] ?? "", lines: [] });
|
|
20241
|
+
continue;
|
|
20242
|
+
}
|
|
20243
|
+
sections[sections.length - 1]?.lines.push(line);
|
|
20244
|
+
}
|
|
20245
|
+
const criteriaSection = sections.find((section) => CRITERIA_HEADING.test(section.title));
|
|
20246
|
+
const summarySection = sections.find((section) => SUMMARY_HEADING.test(section.title));
|
|
20247
|
+
const fallback = sections.find(
|
|
20248
|
+
(section) => section !== criteriaSection && section.lines.some((line) => line.trim() !== "")
|
|
20249
|
+
);
|
|
20250
|
+
const summarySource = summarySection ?? fallback;
|
|
20251
|
+
const summary = collapse((summarySource?.lines ?? []).join("\n"));
|
|
20252
|
+
const criteria = (criteriaSection?.lines ?? []).map((line) => BULLET.exec(line)?.[1]?.trim()).filter((line) => Boolean(line));
|
|
20253
|
+
return { summary, criteria };
|
|
20254
|
+
}
|
|
20255
|
+
function collapse(text) {
|
|
20256
|
+
return text.split("\n").map((line) => line.trim()).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
20257
|
+
}
|
|
20258
|
+
function toIssue(raw) {
|
|
20259
|
+
const parsed = parseIssueBody(raw.body ?? "");
|
|
20260
|
+
return {
|
|
20261
|
+
number: raw.number,
|
|
20262
|
+
title: raw.title,
|
|
20263
|
+
labels: (raw.labels ?? []).map((label) => label.name),
|
|
20264
|
+
state: raw.state ?? "OPEN",
|
|
20265
|
+
updatedAt: raw.updatedAt ?? (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
20266
|
+
summary: parsed.summary,
|
|
20267
|
+
criteria: parsed.criteria
|
|
20268
|
+
};
|
|
20269
|
+
}
|
|
20270
|
+
async function fetchIssues(input2) {
|
|
20271
|
+
const args = [
|
|
20272
|
+
"issue",
|
|
20273
|
+
"list",
|
|
20274
|
+
"--repo",
|
|
20275
|
+
`${input2.repo.owner}/${input2.repo.name}`,
|
|
20276
|
+
"--state",
|
|
20277
|
+
"open",
|
|
20278
|
+
"--limit",
|
|
20279
|
+
String(input2.config.issueLimit),
|
|
20280
|
+
"--json",
|
|
20281
|
+
"number,title,labels,body,state,updatedAt"
|
|
20282
|
+
];
|
|
20283
|
+
if (input2.config.issueLabelFilter) args.push("--label", input2.config.issueLabelFilter);
|
|
20284
|
+
const { stdout } = await input2.runner("gh", args, { cwd: input2.cwd });
|
|
20285
|
+
const rows = JSON.parse(stdout || "[]");
|
|
20286
|
+
return rows.map(toIssue).sort((left, right) => left.number - right.number);
|
|
20287
|
+
}
|
|
20288
|
+
function issueCachePath(paths, repo) {
|
|
20289
|
+
return join6(paths.issueCacheDir, `${repo.owner}__${repo.name}.json`);
|
|
20290
|
+
}
|
|
20291
|
+
function normalizeCachedIssue(value) {
|
|
20292
|
+
if (!value || typeof value !== "object") return null;
|
|
20293
|
+
const issue2 = value;
|
|
20294
|
+
if (typeof issue2.number !== "number" || typeof issue2.title !== "string") return null;
|
|
20295
|
+
return {
|
|
20296
|
+
number: issue2.number,
|
|
20297
|
+
title: issue2.title,
|
|
20298
|
+
labels: Array.isArray(issue2.labels) ? issue2.labels.filter((label) => typeof label === "string") : [],
|
|
20299
|
+
state: typeof issue2.state === "string" ? issue2.state : "OPEN",
|
|
20300
|
+
updatedAt: typeof issue2.updatedAt === "string" ? issue2.updatedAt : (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
20301
|
+
summary: typeof issue2.summary === "string" ? issue2.summary : "",
|
|
20302
|
+
criteria: Array.isArray(issue2.criteria) ? issue2.criteria.filter((criterion) => typeof criterion === "string") : []
|
|
20303
|
+
};
|
|
20304
|
+
}
|
|
20305
|
+
function readIssueCache(path) {
|
|
20306
|
+
try {
|
|
20307
|
+
const parsed = JSON.parse(readFileSync4(path, "utf8"));
|
|
20308
|
+
if (!Array.isArray(parsed.issues) || typeof parsed.fetchedAt !== "number") {
|
|
20309
|
+
rmSync2(path, { force: true });
|
|
20310
|
+
return null;
|
|
20311
|
+
}
|
|
20312
|
+
const issues = parsed.issues.map(normalizeCachedIssue);
|
|
20313
|
+
if (issues.some((issue2) => issue2 === null)) {
|
|
20314
|
+
rmSync2(path, { force: true });
|
|
20315
|
+
return null;
|
|
20316
|
+
}
|
|
20317
|
+
return { fetchedAt: parsed.fetchedAt, issues };
|
|
20318
|
+
} catch {
|
|
20319
|
+
try {
|
|
20320
|
+
rmSync2(path, { force: true });
|
|
20321
|
+
} catch {
|
|
20322
|
+
}
|
|
20323
|
+
return null;
|
|
20324
|
+
}
|
|
20325
|
+
}
|
|
20326
|
+
function writeIssueCache(path, cache) {
|
|
20327
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
20328
|
+
const temporary = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
20329
|
+
writeFileSync3(temporary, `${JSON.stringify(cache, null, 2)}
|
|
20330
|
+
`);
|
|
20331
|
+
renameSync2(temporary, path);
|
|
20332
|
+
}
|
|
20333
|
+
var CACHE_TTL_MS = 6e4;
|
|
20334
|
+
function classifyGhError(error62) {
|
|
20335
|
+
const message = error62 instanceof Error ? error62.message : String(error62);
|
|
20336
|
+
if (/not authenticated|authentication|401|gh auth login/i.test(message)) {
|
|
20337
|
+
return { code: "auth", message };
|
|
20338
|
+
}
|
|
20339
|
+
if (/rate limit|secondary rate|403/i.test(message)) {
|
|
20340
|
+
return { code: "rate_limit", message };
|
|
20341
|
+
}
|
|
20342
|
+
if (/ENOTFOUND|ECONNREFUSED|ETIMEDOUT|fetch failed|network/i.test(message)) {
|
|
20343
|
+
return { code: "network", message };
|
|
20344
|
+
}
|
|
20345
|
+
if (/^gh\b|gh issue/i.test(message)) return { code: "gh", message };
|
|
20346
|
+
return { code: "unknown", message };
|
|
20347
|
+
}
|
|
20348
|
+
async function fetchIssue(input2) {
|
|
20349
|
+
const { stdout } = await input2.runner(
|
|
20350
|
+
"gh",
|
|
20351
|
+
[
|
|
20352
|
+
"issue",
|
|
20353
|
+
"view",
|
|
20354
|
+
String(input2.number),
|
|
20355
|
+
"--repo",
|
|
20356
|
+
`${input2.repo.owner}/${input2.repo.name}`,
|
|
20357
|
+
"--json",
|
|
20358
|
+
"number,title,labels,body,state,updatedAt"
|
|
20359
|
+
],
|
|
20360
|
+
{ cwd: input2.cwd }
|
|
20361
|
+
);
|
|
20362
|
+
return toIssue(JSON.parse(stdout));
|
|
20363
|
+
}
|
|
20364
|
+
function availableLabels(issues) {
|
|
20365
|
+
const seen = /* @__PURE__ */ new Map();
|
|
20366
|
+
for (const issue2 of issues) {
|
|
20367
|
+
for (const label of issue2.labels) {
|
|
20368
|
+
const key = label.toLocaleLowerCase();
|
|
20369
|
+
if (!seen.has(key)) seen.set(key, label);
|
|
20370
|
+
}
|
|
20371
|
+
}
|
|
20372
|
+
return [...seen.values()].sort((left, right) => left.localeCompare(right));
|
|
20373
|
+
}
|
|
20374
|
+
async function loadIssuesResult(input2) {
|
|
20375
|
+
const path = issueCachePath(input2.paths, input2.repo);
|
|
20376
|
+
const now = input2.now ?? Date.now();
|
|
20377
|
+
const cache = readIssueCache(path);
|
|
20378
|
+
if (!input2.force && cache && now - cache.fetchedAt < CACHE_TTL_MS) {
|
|
20379
|
+
return { issues: cache.issues, cached: true, stale: false, fetchedAt: cache.fetchedAt, error: null };
|
|
20380
|
+
}
|
|
20381
|
+
try {
|
|
20382
|
+
const issues = await fetchIssues({
|
|
20383
|
+
repo: input2.repo,
|
|
20384
|
+
config: input2.config,
|
|
20385
|
+
runner: input2.runner,
|
|
20386
|
+
cwd: input2.cwd
|
|
20387
|
+
});
|
|
20388
|
+
writeIssueCache(path, { fetchedAt: now, issues });
|
|
20389
|
+
return { issues, cached: false, stale: false, fetchedAt: now, error: null };
|
|
20390
|
+
} catch (error62) {
|
|
20391
|
+
const failure3 = classifyGhError(error62);
|
|
20392
|
+
if (cache) {
|
|
20393
|
+
return {
|
|
20394
|
+
issues: cache.issues,
|
|
20395
|
+
cached: true,
|
|
20396
|
+
stale: true,
|
|
20397
|
+
fetchedAt: cache.fetchedAt,
|
|
20398
|
+
error: failure3
|
|
20399
|
+
};
|
|
20400
|
+
}
|
|
20401
|
+
return { issues: [], cached: false, stale: false, fetchedAt: null, error: failure3 };
|
|
20402
|
+
}
|
|
20403
|
+
}
|
|
20404
|
+
|
|
20405
|
+
// packages/core/src/skills.ts
|
|
20406
|
+
import { createHash } from "node:crypto";
|
|
20407
|
+
import { cpSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync5, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
20408
|
+
import { join as join7, relative, sep as sep2 } from "node:path";
|
|
20409
|
+
var MANIFEST_NAME = ".skills-deck.json";
|
|
20410
|
+
function hashSkillDir(dir) {
|
|
20411
|
+
const hash2 = createHash("sha256");
|
|
20412
|
+
const walk = (current) => {
|
|
20413
|
+
const entries = readdirSync2(current, { withFileTypes: true }).sort(
|
|
20414
|
+
(left, right) => left.name.localeCompare(right.name)
|
|
20415
|
+
);
|
|
20416
|
+
for (const entry of entries) {
|
|
20417
|
+
const absolute = join7(current, entry.name);
|
|
20418
|
+
const key = relative(dir, absolute).split(sep2).join("/");
|
|
20419
|
+
if (entry.isDirectory()) {
|
|
20420
|
+
hash2.update(`d:${key}
|
|
20421
|
+
`);
|
|
20422
|
+
walk(absolute);
|
|
20423
|
+
} else if (entry.isFile()) {
|
|
20424
|
+
hash2.update(`f:${key}
|
|
20425
|
+
`);
|
|
20426
|
+
hash2.update(readFileSync5(absolute));
|
|
20427
|
+
hash2.update("\n");
|
|
20428
|
+
}
|
|
20429
|
+
}
|
|
20430
|
+
};
|
|
20431
|
+
walk(dir);
|
|
20432
|
+
return hash2.digest("hex");
|
|
20433
|
+
}
|
|
20434
|
+
function readManifest(targetDir) {
|
|
20435
|
+
try {
|
|
20436
|
+
const parsed = JSON.parse(readFileSync5(join7(targetDir, MANIFEST_NAME), "utf8"));
|
|
20437
|
+
if (!parsed || typeof parsed.version !== "string" || typeof parsed.skills !== "object") return null;
|
|
20438
|
+
return parsed;
|
|
20439
|
+
} catch {
|
|
20440
|
+
return null;
|
|
20441
|
+
}
|
|
20442
|
+
}
|
|
20443
|
+
function writeManifest(targetDir, manifest) {
|
|
20444
|
+
writeFileSync4(join7(targetDir, MANIFEST_NAME), `${JSON.stringify(manifest, null, 2)}
|
|
20445
|
+
`);
|
|
20446
|
+
}
|
|
20447
|
+
function readBundledVersion(sourceDir) {
|
|
20448
|
+
try {
|
|
20449
|
+
return readFileSync5(join7(sourceDir, "VERSION"), "utf8").trim() || "unknown";
|
|
20450
|
+
} catch {
|
|
20451
|
+
return "unknown";
|
|
20452
|
+
}
|
|
20453
|
+
}
|
|
20454
|
+
function skillNames(sourceDir) {
|
|
20455
|
+
if (!existsSync3(sourceDir)) return [];
|
|
20456
|
+
return readdirSync2(sourceDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => existsSync3(join7(sourceDir, name, "SKILL.md"))).sort();
|
|
20457
|
+
}
|
|
20458
|
+
function bundledSkillNames(sourceDir) {
|
|
20459
|
+
return skillNames(sourceDir);
|
|
20460
|
+
}
|
|
20461
|
+
function installBundledSkills(input2) {
|
|
20462
|
+
const result = { installed: [], updated: [], skipped: [], unchanged: [] };
|
|
20463
|
+
if (!existsSync3(input2.sourceDir)) return result;
|
|
20464
|
+
mkdirSync3(input2.targetDir, { recursive: true });
|
|
20465
|
+
const version2 = readBundledVersion(input2.sourceDir);
|
|
20466
|
+
const manifest = readManifest(input2.targetDir) ?? { version: version2, skills: {} };
|
|
20467
|
+
const now = new Date(input2.now ?? Date.now()).toISOString();
|
|
20468
|
+
for (const name of skillNames(input2.sourceDir)) {
|
|
20469
|
+
const source = join7(input2.sourceDir, name);
|
|
20470
|
+
const target = join7(input2.targetDir, name);
|
|
20471
|
+
const sourceHash = hashSkillDir(source);
|
|
20472
|
+
if (!existsSync3(target)) {
|
|
20473
|
+
cpSync(source, target, { recursive: true });
|
|
20474
|
+
manifest.skills[name] = { hash: sourceHash, updatedAt: now };
|
|
20475
|
+
result.installed.push(name);
|
|
20476
|
+
continue;
|
|
20477
|
+
}
|
|
20478
|
+
const recorded = manifest.skills[name];
|
|
20479
|
+
const targetHash = hashSkillDir(target);
|
|
20480
|
+
if (!recorded) {
|
|
20481
|
+
if (targetHash === sourceHash) {
|
|
20482
|
+
manifest.skills[name] = { hash: targetHash, updatedAt: now };
|
|
20483
|
+
result.unchanged.push(name);
|
|
20484
|
+
} else {
|
|
20485
|
+
result.skipped.push(name);
|
|
20486
|
+
}
|
|
20487
|
+
continue;
|
|
20488
|
+
}
|
|
20489
|
+
if (recorded.hash !== targetHash) {
|
|
20490
|
+
result.skipped.push(name);
|
|
20491
|
+
continue;
|
|
20492
|
+
}
|
|
20493
|
+
if (sourceHash === targetHash) {
|
|
20494
|
+
result.unchanged.push(name);
|
|
20495
|
+
continue;
|
|
20496
|
+
}
|
|
20497
|
+
rmSync3(target, { recursive: true, force: true });
|
|
20498
|
+
cpSync(source, target, { recursive: true });
|
|
20499
|
+
manifest.skills[name] = { hash: sourceHash, updatedAt: now };
|
|
20500
|
+
result.updated.push(name);
|
|
20501
|
+
}
|
|
20502
|
+
manifest.version = version2;
|
|
20503
|
+
writeManifest(input2.targetDir, manifest);
|
|
20504
|
+
return result;
|
|
20505
|
+
}
|
|
20506
|
+
|
|
19828
20507
|
// packages/core/src/version.ts
|
|
19829
20508
|
var SUPPORTED_OPENCODE_MIN = "2.3.0";
|
|
19830
20509
|
var SUPPORTED_OPENCODE_MAX_EXCLUSIVE = "3.0.0";
|
|
19831
20510
|
var SUPPORTED_OPENCODE_RANGE = `>=${SUPPORTED_OPENCODE_MIN} <${SUPPORTED_OPENCODE_MAX_EXCLUSIVE}`;
|
|
20511
|
+
var VERSION = /(\d+)\.(\d+)\.(\d+)/;
|
|
20512
|
+
function parseVersion(input2) {
|
|
20513
|
+
if (!input2) return null;
|
|
20514
|
+
const match = VERSION.exec(input2);
|
|
20515
|
+
if (!match) return null;
|
|
20516
|
+
const major = Number(match[1]);
|
|
20517
|
+
const minor = Number(match[2]);
|
|
20518
|
+
const patch = Number(match[3]);
|
|
20519
|
+
if (!Number.isInteger(major) || !Number.isInteger(minor) || !Number.isInteger(patch)) return null;
|
|
20520
|
+
return [major, minor, patch];
|
|
20521
|
+
}
|
|
20522
|
+
function compareVersions(left, right) {
|
|
20523
|
+
for (let index = 0; index < 3; index += 1) {
|
|
20524
|
+
const a = left[index] ?? 0;
|
|
20525
|
+
const b = right[index] ?? 0;
|
|
20526
|
+
if (a !== b) return a < b ? -1 : 1;
|
|
20527
|
+
}
|
|
20528
|
+
return 0;
|
|
20529
|
+
}
|
|
20530
|
+
function isSupportedVersion(input2) {
|
|
20531
|
+
const parsed = parseVersion(input2);
|
|
20532
|
+
if (!parsed) return false;
|
|
20533
|
+
return compareVersions(parsed, [2, 3, 0]) >= 0 && compareVersions(parsed, [3, 0, 0]) < 0;
|
|
20534
|
+
}
|
|
20535
|
+
function checkOpencodeVersion(input2) {
|
|
20536
|
+
const parsed = parseVersion(input2);
|
|
20537
|
+
if (!parsed) {
|
|
20538
|
+
return {
|
|
20539
|
+
ok: false,
|
|
20540
|
+
version: null,
|
|
20541
|
+
message: `OpenCode \u306E\u30D0\u30FC\u30B8\u30E7\u30F3\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\uFF08\u5BFE\u5FDC: ${SUPPORTED_OPENCODE_RANGE}\uFF09`
|
|
20542
|
+
};
|
|
20543
|
+
}
|
|
20544
|
+
const version2 = parsed.join(".");
|
|
20545
|
+
if (isSupportedVersion(version2)) return { ok: true, version: version2, message: "" };
|
|
20546
|
+
return {
|
|
20547
|
+
ok: false,
|
|
20548
|
+
version: version2,
|
|
20549
|
+
message: `OpenCode ${version2} \u306F\u672A\u5BFE\u5FDC\u3067\u3059\uFF08\u5BFE\u5FDC: ${SUPPORTED_OPENCODE_RANGE}\uFF09\u3002\u52D5\u4F5C\u306F\u4FDD\u8A3C\u3055\u308C\u307E\u305B\u3093`
|
|
20550
|
+
};
|
|
20551
|
+
}
|
|
20552
|
+
|
|
20553
|
+
// packages/plugin/src/contract.ts
|
|
20554
|
+
import { Rpc } from "@opencode/plugin/rpc";
|
|
20555
|
+
var repoViewSchema = external_exports.object({
|
|
20556
|
+
/** `owner/name`。 */
|
|
20557
|
+
repo: external_exports.string(),
|
|
20558
|
+
/** メインの作業ツリー(リンクされた worktree から解決してもここに戻す)。 */
|
|
20559
|
+
root: external_exports.string(),
|
|
20560
|
+
branch: external_exports.string(),
|
|
20561
|
+
defaultBranch: external_exports.string(),
|
|
20562
|
+
/** `config.json` の `repos` から来たか。 */
|
|
20563
|
+
configured: external_exports.boolean()
|
|
20564
|
+
});
|
|
20565
|
+
var issueViewSchema = external_exports.object({
|
|
20566
|
+
number: external_exports.number().int(),
|
|
20567
|
+
title: external_exports.string(),
|
|
20568
|
+
labels: external_exports.array(external_exports.string()),
|
|
20569
|
+
state: external_exports.string(),
|
|
20570
|
+
updatedAt: external_exports.string(),
|
|
20571
|
+
summary: external_exports.string(),
|
|
20572
|
+
criteria: external_exports.array(external_exports.string())
|
|
20573
|
+
});
|
|
20574
|
+
var worktreeViewSchema = external_exports.object({
|
|
20575
|
+
directory: external_exports.string(),
|
|
20576
|
+
branch: external_exports.string().nullable(),
|
|
20577
|
+
issueNumber: external_exports.number().int().nullable(),
|
|
20578
|
+
registered: external_exports.boolean(),
|
|
20579
|
+
exists: external_exports.boolean(),
|
|
20580
|
+
dirty: external_exports.boolean(),
|
|
20581
|
+
/** そのディレクトリを使っているアプリ内セッションの数。 */
|
|
20582
|
+
sessions: external_exports.number().int()
|
|
20583
|
+
});
|
|
20584
|
+
var syncViewSchema = external_exports.object({
|
|
20585
|
+
state: external_exports.enum(["fresh", "cached", "stale", "error"]),
|
|
20586
|
+
fetchedAt: external_exports.number().nullable(),
|
|
20587
|
+
message: external_exports.string().nullable(),
|
|
20588
|
+
code: external_exports.enum(["auth", "rate_limit", "network", "gh", "unknown"]).nullable()
|
|
20589
|
+
});
|
|
20590
|
+
var versionViewSchema = external_exports.object({
|
|
20591
|
+
app: external_exports.string().nullable(),
|
|
20592
|
+
supported: external_exports.boolean(),
|
|
20593
|
+
message: external_exports.string()
|
|
20594
|
+
});
|
|
20595
|
+
var promptsSchema = external_exports.object({
|
|
20596
|
+
/** `draftPromptIssue`。`{number}` を置換して使う。 */
|
|
20597
|
+
issue: external_exports.string(),
|
|
20598
|
+
/** `draftPromptSkill`。`{skill}` を置換して使う。 */
|
|
20599
|
+
skill: external_exports.string()
|
|
20600
|
+
});
|
|
20601
|
+
var skillsViewSchema = external_exports.object({
|
|
20602
|
+
/** 同梱スキルの名前。 */
|
|
20603
|
+
available: external_exports.array(external_exports.string()),
|
|
20604
|
+
/** 実際に配置した(または配置済みだった)スキルの名前。 */
|
|
20605
|
+
installed: external_exports.array(external_exports.string())
|
|
20606
|
+
});
|
|
20607
|
+
var boardSnapshotSchema = external_exports.object({
|
|
20608
|
+
/** 表示中の Repo。解決できなければ null。 */
|
|
20609
|
+
repo: repoViewSchema.nullable(),
|
|
20610
|
+
repos: external_exports.array(repoViewSchema),
|
|
20611
|
+
issues: external_exports.array(issueViewSchema),
|
|
20612
|
+
worktrees: external_exports.array(worktreeViewSchema),
|
|
20613
|
+
/** 検索・ラベル絞り込みのデータ面。 */
|
|
20614
|
+
labels: external_exports.array(external_exports.string()),
|
|
20615
|
+
sync: syncViewSchema,
|
|
20616
|
+
prompts: promptsSchema,
|
|
20617
|
+
version: versionViewSchema,
|
|
20618
|
+
skills: skillsViewSchema,
|
|
20619
|
+
/** Repo が無い・設定が壊れているなど、snapshot 全体の失敗。 */
|
|
20620
|
+
error: external_exports.string().nullable()
|
|
20621
|
+
});
|
|
20622
|
+
var failureSchema = external_exports.object({
|
|
20623
|
+
code: external_exports.enum(["repo", "issue", "gh", "git", "worktree", "session", "unknown"]),
|
|
20624
|
+
message: external_exports.string()
|
|
20625
|
+
});
|
|
20626
|
+
var ControlTower = Rpc.define({
|
|
20627
|
+
id: "opencode-skills-deck.control-tower",
|
|
20628
|
+
methods: {
|
|
20629
|
+
boardSnapshot: {
|
|
20630
|
+
input: external_exports.object({
|
|
20631
|
+
/** `owner/name`。省略時はセッションのディレクトリから解決した Repo。 */
|
|
20632
|
+
repo: external_exports.string().optional(),
|
|
20633
|
+
/** gh を呼び直す。 */
|
|
20634
|
+
force: external_exports.boolean().optional()
|
|
20635
|
+
}).optional(),
|
|
20636
|
+
output: boardSnapshotSchema
|
|
20637
|
+
},
|
|
20638
|
+
issueDetail: {
|
|
20639
|
+
input: external_exports.object({
|
|
20640
|
+
repo: external_exports.string().optional(),
|
|
20641
|
+
number: external_exports.number().int()
|
|
20642
|
+
}),
|
|
20643
|
+
output: external_exports.object({
|
|
20644
|
+
issue: issueViewSchema.nullable(),
|
|
20645
|
+
worktree: worktreeViewSchema.nullable(),
|
|
20646
|
+
error: failureSchema.nullable()
|
|
20647
|
+
})
|
|
20648
|
+
},
|
|
20649
|
+
repoList: {
|
|
20650
|
+
input: external_exports.object({ repo: external_exports.string().optional() }).optional(),
|
|
20651
|
+
output: external_exports.object({
|
|
20652
|
+
repos: external_exports.array(repoViewSchema),
|
|
20653
|
+
active: repoViewSchema.nullable()
|
|
20654
|
+
})
|
|
20655
|
+
},
|
|
20656
|
+
repoSwitch: {
|
|
20657
|
+
input: external_exports.object({ repo: external_exports.string() }),
|
|
20658
|
+
output: boardSnapshotSchema
|
|
20659
|
+
},
|
|
20660
|
+
dispatchPrepare: {
|
|
20661
|
+
input: external_exports.object({
|
|
20662
|
+
repo: external_exports.string().optional(),
|
|
20663
|
+
number: external_exports.number().int()
|
|
20664
|
+
}),
|
|
20665
|
+
output: external_exports.discriminatedUnion("ok", [
|
|
20666
|
+
external_exports.object({
|
|
20667
|
+
ok: external_exports.literal(true),
|
|
20668
|
+
issue: issueViewSchema,
|
|
20669
|
+
worktree: worktreeViewSchema,
|
|
20670
|
+
base: external_exports.string(),
|
|
20671
|
+
created: external_exports.boolean(),
|
|
20672
|
+
draft: external_exports.string()
|
|
20673
|
+
}),
|
|
20674
|
+
external_exports.object({ ok: external_exports.literal(false), error: failureSchema })
|
|
20675
|
+
])
|
|
20676
|
+
},
|
|
20677
|
+
/**
|
|
20678
|
+
* New session 用に、worktree を用意して**その worktree で動くセッション**を作る(#40)。
|
|
20679
|
+
* アプリ 2.3 の `layout.draft` は directory を実行先に使わないため、セッションは server 側で作る。
|
|
20680
|
+
* 送信はしない。プロンプトは GUI が新しいセッションの composer に入れる。
|
|
20681
|
+
*/
|
|
20682
|
+
sessionStart: {
|
|
20683
|
+
input: external_exports.object({
|
|
20684
|
+
repo: external_exports.string().optional(),
|
|
20685
|
+
number: external_exports.number().int()
|
|
20686
|
+
}),
|
|
20687
|
+
output: external_exports.discriminatedUnion("ok", [
|
|
20688
|
+
external_exports.object({
|
|
20689
|
+
ok: external_exports.literal(true),
|
|
20690
|
+
sessionID: external_exports.string(),
|
|
20691
|
+
directory: external_exports.string(),
|
|
20692
|
+
worktree: worktreeViewSchema,
|
|
20693
|
+
draft: external_exports.string()
|
|
20694
|
+
}),
|
|
20695
|
+
external_exports.object({ ok: external_exports.literal(false), error: failureSchema })
|
|
20696
|
+
])
|
|
20697
|
+
},
|
|
20698
|
+
worktreeCleanup: {
|
|
20699
|
+
input: external_exports.object({
|
|
20700
|
+
repo: external_exports.string().optional(),
|
|
20701
|
+
number: external_exports.number().int().optional(),
|
|
20702
|
+
directory: external_exports.string().optional(),
|
|
20703
|
+
/** GUI 上でその worktree のタブが開いているか。 */
|
|
20704
|
+
inUse: external_exports.boolean().optional()
|
|
20705
|
+
}),
|
|
20706
|
+
output: external_exports.object({
|
|
20707
|
+
removed: external_exports.boolean(),
|
|
20708
|
+
reason: external_exports.enum(["removed", "missing", "dirty", "in_use", "outside", "unknown"]),
|
|
20709
|
+
message: external_exports.string().nullable()
|
|
20710
|
+
})
|
|
20711
|
+
},
|
|
20712
|
+
worktreeOrphans: {
|
|
20713
|
+
input: external_exports.object({ repo: external_exports.string().optional() }).optional(),
|
|
20714
|
+
output: external_exports.object({
|
|
20715
|
+
orphans: external_exports.array(worktreeViewSchema),
|
|
20716
|
+
error: failureSchema.nullable()
|
|
20717
|
+
})
|
|
20718
|
+
},
|
|
20719
|
+
sessionResume: {
|
|
20720
|
+
input: external_exports.object({
|
|
20721
|
+
repo: external_exports.string().optional(),
|
|
20722
|
+
number: external_exports.number().int()
|
|
20723
|
+
}),
|
|
20724
|
+
output: external_exports.discriminatedUnion("ok", [
|
|
20725
|
+
external_exports.object({
|
|
20726
|
+
ok: external_exports.literal(true),
|
|
20727
|
+
worktree: worktreeViewSchema.nullable(),
|
|
20728
|
+
draft: external_exports.string(),
|
|
20729
|
+
existing: external_exports.boolean()
|
|
20730
|
+
}),
|
|
20731
|
+
external_exports.object({ ok: external_exports.literal(false), error: failureSchema })
|
|
20732
|
+
])
|
|
20733
|
+
}
|
|
20734
|
+
},
|
|
20735
|
+
events: {
|
|
20736
|
+
boardChanged: {
|
|
20737
|
+
schema: external_exports.object({
|
|
20738
|
+
repo: external_exports.string(),
|
|
20739
|
+
fetchedAt: external_exports.number()
|
|
20740
|
+
})
|
|
20741
|
+
}
|
|
20742
|
+
}
|
|
20743
|
+
});
|
|
20744
|
+
|
|
20745
|
+
// packages/plugin/src/rpc-handlers.ts
|
|
20746
|
+
function toRepoView(context) {
|
|
20747
|
+
return {
|
|
20748
|
+
repo: context.repo,
|
|
20749
|
+
root: context.root,
|
|
20750
|
+
branch: context.info?.branch ?? "?",
|
|
20751
|
+
defaultBranch: context.info?.defaultBranch ?? "main",
|
|
20752
|
+
configured: context.configured
|
|
20753
|
+
};
|
|
20754
|
+
}
|
|
20755
|
+
function toIssueView(issue2) {
|
|
20756
|
+
return {
|
|
20757
|
+
number: issue2.number,
|
|
20758
|
+
title: issue2.title,
|
|
20759
|
+
labels: issue2.labels,
|
|
20760
|
+
state: issue2.state,
|
|
20761
|
+
updatedAt: issue2.updatedAt,
|
|
20762
|
+
summary: issue2.summary,
|
|
20763
|
+
criteria: issue2.criteria
|
|
20764
|
+
};
|
|
20765
|
+
}
|
|
20766
|
+
function toSyncView(result) {
|
|
20767
|
+
let state;
|
|
20768
|
+
if (result.error) state = result.cached ? "stale" : "error";
|
|
20769
|
+
else state = result.cached ? "cached" : "fresh";
|
|
20770
|
+
return {
|
|
20771
|
+
state,
|
|
20772
|
+
fetchedAt: result.fetchedAt,
|
|
20773
|
+
message: result.error?.message ?? null,
|
|
20774
|
+
code: result.error?.code ?? null
|
|
20775
|
+
};
|
|
20776
|
+
}
|
|
20777
|
+
function versionView(appVersion) {
|
|
20778
|
+
const check2 = checkOpencodeVersion(appVersion);
|
|
20779
|
+
return { app: check2.version, supported: check2.ok, message: check2.message };
|
|
20780
|
+
}
|
|
20781
|
+
function failure2(code, error62) {
|
|
20782
|
+
return { code, message: error62 instanceof Error ? error62.message : String(error62) };
|
|
20783
|
+
}
|
|
20784
|
+
async function resolveActive(input2) {
|
|
20785
|
+
const contexts = await resolveRepoContexts({
|
|
20786
|
+
config: input2.ctx.config,
|
|
20787
|
+
directory: input2.ctx.directory,
|
|
20788
|
+
runner: input2.ctx.runner
|
|
20789
|
+
});
|
|
20790
|
+
const active = input2.repo ? contexts.find((context) => context.repo === input2.repo) ?? null : contexts[0] ?? null;
|
|
20791
|
+
return { repos: contexts.map(toRepoView), active, contexts };
|
|
20792
|
+
}
|
|
20793
|
+
async function toWorktreeView(ctx, status) {
|
|
20794
|
+
const sessions = ctx.sessionsIn ? await ctx.sessionsIn(status.directory) : 0;
|
|
20795
|
+
return {
|
|
20796
|
+
directory: status.directory,
|
|
20797
|
+
branch: status.branch,
|
|
20798
|
+
issueNumber: status.issueNumber,
|
|
20799
|
+
registered: status.registered,
|
|
20800
|
+
exists: status.exists,
|
|
20801
|
+
dirty: status.dirty,
|
|
20802
|
+
sessions
|
|
20803
|
+
};
|
|
20804
|
+
}
|
|
20805
|
+
async function worktreeForIssue(ctx, repoRoot, issueNumber) {
|
|
20806
|
+
const directory = issueWorktreePath(repoRoot, issueNumber);
|
|
20807
|
+
const status = await readWorktreeStatus({
|
|
20808
|
+
repoRoot,
|
|
20809
|
+
directory,
|
|
20810
|
+
runner: ctx.runner
|
|
20811
|
+
});
|
|
20812
|
+
return toWorktreeView(ctx, status);
|
|
20813
|
+
}
|
|
20814
|
+
async function snapshot(input2) {
|
|
20815
|
+
const { repos, active } = await resolveActive(input2);
|
|
20816
|
+
const base = {
|
|
20817
|
+
repo: active ? toRepoView(active) : null,
|
|
20818
|
+
repos,
|
|
20819
|
+
issues: [],
|
|
20820
|
+
worktrees: [],
|
|
20821
|
+
labels: [],
|
|
20822
|
+
sync: { state: "error", fetchedAt: null, message: null, code: null },
|
|
20823
|
+
prompts: {
|
|
20824
|
+
issue: input2.ctx.config.draftPromptIssue,
|
|
20825
|
+
skill: input2.ctx.config.draftPromptSkill
|
|
20826
|
+
},
|
|
20827
|
+
version: versionView(input2.ctx.appVersion),
|
|
20828
|
+
skills: input2.ctx.skills,
|
|
20829
|
+
error: null
|
|
20830
|
+
};
|
|
20831
|
+
if (!active) {
|
|
20832
|
+
return {
|
|
20833
|
+
...base,
|
|
20834
|
+
error: repos.length === 0 ? "Could not resolve a repo context. Open a git repository or set repos in config.json." : `Repo ${input2.repo ?? ""} was not found.`
|
|
20835
|
+
};
|
|
20836
|
+
}
|
|
20837
|
+
const result = await loadIssuesResult({
|
|
20838
|
+
paths: input2.ctx.paths,
|
|
20839
|
+
repo: repoRef(active),
|
|
20840
|
+
config: input2.ctx.config,
|
|
20841
|
+
runner: input2.ctx.runner,
|
|
20842
|
+
cwd: active.root,
|
|
20843
|
+
force: input2.force,
|
|
20844
|
+
now: input2.ctx.now
|
|
20845
|
+
});
|
|
20846
|
+
const worktrees = await listIssueWorktrees({
|
|
20847
|
+
repoRoot: active.root,
|
|
20848
|
+
runner: input2.ctx.runner
|
|
20849
|
+
});
|
|
20850
|
+
const views = [];
|
|
20851
|
+
for (const worktree of worktrees) views.push(await toWorktreeView(input2.ctx, worktree));
|
|
20852
|
+
return {
|
|
20853
|
+
...base,
|
|
20854
|
+
issues: [...result.issues].sort((left, right) => left.number - right.number).map(toIssueView),
|
|
20855
|
+
worktrees: views,
|
|
20856
|
+
labels: availableLabels(result.issues),
|
|
20857
|
+
sync: toSyncView(result)
|
|
20858
|
+
};
|
|
20859
|
+
}
|
|
20860
|
+
function emitChanged(ctx, repo) {
|
|
20861
|
+
ctx.emit?.("boardChanged", { repo, fetchedAt: ctx.now });
|
|
20862
|
+
}
|
|
20863
|
+
function createControlTowerHandlers(getContext) {
|
|
20864
|
+
return {
|
|
20865
|
+
async boardSnapshot(input2) {
|
|
20866
|
+
return snapshot({ ctx: await getContext(), repo: input2?.repo, force: input2?.force });
|
|
20867
|
+
},
|
|
20868
|
+
async issueDetail(input2) {
|
|
20869
|
+
const ctx = await getContext();
|
|
20870
|
+
const { active } = await resolveActive({ ctx, repo: input2.repo });
|
|
20871
|
+
if (!active) return { issue: null, worktree: null, error: failure2("repo", "Repo was not found.") };
|
|
20872
|
+
const result = await loadIssuesResult({
|
|
20873
|
+
paths: ctx.paths,
|
|
20874
|
+
repo: repoRef(active),
|
|
20875
|
+
config: ctx.config,
|
|
20876
|
+
runner: ctx.runner,
|
|
20877
|
+
cwd: active.root,
|
|
20878
|
+
now: ctx.now
|
|
20879
|
+
});
|
|
20880
|
+
let issue2 = result.issues.find((candidate) => candidate.number === input2.number);
|
|
20881
|
+
if (!issue2) {
|
|
20882
|
+
try {
|
|
20883
|
+
issue2 = await fetchIssue({
|
|
20884
|
+
repo: repoRef(active),
|
|
20885
|
+
number: input2.number,
|
|
20886
|
+
runner: ctx.runner,
|
|
20887
|
+
cwd: active.root
|
|
20888
|
+
});
|
|
20889
|
+
} catch (error62) {
|
|
20890
|
+
return {
|
|
20891
|
+
issue: null,
|
|
20892
|
+
worktree: null,
|
|
20893
|
+
error: failure2("issue", classifyGhError(error62).message)
|
|
20894
|
+
};
|
|
20895
|
+
}
|
|
20896
|
+
}
|
|
20897
|
+
return {
|
|
20898
|
+
issue: toIssueView(issue2),
|
|
20899
|
+
worktree: await worktreeForIssue(ctx, active.root, input2.number),
|
|
20900
|
+
error: null
|
|
20901
|
+
};
|
|
20902
|
+
},
|
|
20903
|
+
async repoList(input2) {
|
|
20904
|
+
const ctx = await getContext();
|
|
20905
|
+
const { repos, active } = await resolveActive({ ctx, repo: input2?.repo });
|
|
20906
|
+
return { repos, active: active ? toRepoView(active) : null };
|
|
20907
|
+
},
|
|
20908
|
+
async repoSwitch(input2) {
|
|
20909
|
+
return snapshot({ ctx: await getContext(), repo: input2.repo });
|
|
20910
|
+
},
|
|
20911
|
+
async dispatchPrepare(input2) {
|
|
20912
|
+
const ctx = await getContext();
|
|
20913
|
+
const { active } = await resolveActive({ ctx, repo: input2.repo });
|
|
20914
|
+
if (!active) return { ok: false, error: failure2("repo", "Repo was not found.") };
|
|
20915
|
+
let issue2;
|
|
20916
|
+
try {
|
|
20917
|
+
const result = await loadIssuesResult({
|
|
20918
|
+
paths: ctx.paths,
|
|
20919
|
+
repo: repoRef(active),
|
|
20920
|
+
config: ctx.config,
|
|
20921
|
+
runner: ctx.runner,
|
|
20922
|
+
cwd: active.root,
|
|
20923
|
+
now: ctx.now
|
|
20924
|
+
});
|
|
20925
|
+
issue2 = result.issues.find((candidate) => candidate.number === input2.number);
|
|
20926
|
+
} catch (error62) {
|
|
20927
|
+
return { ok: false, error: failure2("gh", classifyGhError(error62).message) };
|
|
20928
|
+
}
|
|
20929
|
+
if (!issue2) {
|
|
20930
|
+
try {
|
|
20931
|
+
issue2 = await fetchIssue({
|
|
20932
|
+
repo: repoRef(active),
|
|
20933
|
+
number: input2.number,
|
|
20934
|
+
runner: ctx.runner,
|
|
20935
|
+
cwd: active.root
|
|
20936
|
+
});
|
|
20937
|
+
} catch (error62) {
|
|
20938
|
+
return { ok: false, error: failure2("issue", classifyGhError(error62).message) };
|
|
20939
|
+
}
|
|
20940
|
+
}
|
|
20941
|
+
try {
|
|
20942
|
+
const plan = await ensureWorktree({
|
|
20943
|
+
repoRoot: active.root,
|
|
20944
|
+
issueNumber: issue2.number,
|
|
20945
|
+
title: issue2.title,
|
|
20946
|
+
baseBranch: ctx.config.baseBranch,
|
|
20947
|
+
runner: ctx.runner
|
|
20948
|
+
});
|
|
20949
|
+
emitChanged(ctx, active.repo);
|
|
20950
|
+
return {
|
|
20951
|
+
ok: true,
|
|
20952
|
+
issue: toIssueView(issue2),
|
|
20953
|
+
worktree: await worktreeForIssue(ctx, active.root, issue2.number),
|
|
20954
|
+
base: plan.base,
|
|
20955
|
+
created: plan.created,
|
|
20956
|
+
draft: fillTemplate(ctx.config.draftPromptIssue, { number: issue2.number })
|
|
20957
|
+
};
|
|
20958
|
+
} catch (error62) {
|
|
20959
|
+
return { ok: false, error: failure2("git", error62) };
|
|
20960
|
+
}
|
|
20961
|
+
},
|
|
20962
|
+
async sessionStart(input2) {
|
|
20963
|
+
const ctx = await getContext();
|
|
20964
|
+
const { active } = await resolveActive({ ctx, repo: input2.repo });
|
|
20965
|
+
if (!active) return { ok: false, error: failure2("repo", "Repo was not found.") };
|
|
20966
|
+
let issue2;
|
|
20967
|
+
try {
|
|
20968
|
+
const result = await loadIssuesResult({
|
|
20969
|
+
paths: ctx.paths,
|
|
20970
|
+
repo: repoRef(active),
|
|
20971
|
+
config: ctx.config,
|
|
20972
|
+
runner: ctx.runner,
|
|
20973
|
+
cwd: active.root,
|
|
20974
|
+
now: ctx.now
|
|
20975
|
+
});
|
|
20976
|
+
issue2 = result.issues.find((candidate) => candidate.number === input2.number);
|
|
20977
|
+
} catch (error62) {
|
|
20978
|
+
return { ok: false, error: failure2("gh", classifyGhError(error62).message) };
|
|
20979
|
+
}
|
|
20980
|
+
if (!issue2) {
|
|
20981
|
+
try {
|
|
20982
|
+
issue2 = await fetchIssue({
|
|
20983
|
+
repo: repoRef(active),
|
|
20984
|
+
number: input2.number,
|
|
20985
|
+
runner: ctx.runner,
|
|
20986
|
+
cwd: active.root
|
|
20987
|
+
});
|
|
20988
|
+
} catch (error62) {
|
|
20989
|
+
return { ok: false, error: failure2("issue", classifyGhError(error62).message) };
|
|
20990
|
+
}
|
|
20991
|
+
}
|
|
20992
|
+
try {
|
|
20993
|
+
const plan = await ensureWorktree({
|
|
20994
|
+
repoRoot: active.root,
|
|
20995
|
+
issueNumber: issue2.number,
|
|
20996
|
+
title: issue2.title,
|
|
20997
|
+
baseBranch: ctx.config.baseBranch,
|
|
20998
|
+
runner: ctx.runner
|
|
20999
|
+
});
|
|
21000
|
+
if (!ctx.createSession) {
|
|
21001
|
+
return { ok: false, error: failure2("session", "Sessions cannot be created here.") };
|
|
21002
|
+
}
|
|
21003
|
+
const sessionID = await ctx.createSession(plan.directory);
|
|
21004
|
+
emitChanged(ctx, active.repo);
|
|
21005
|
+
return {
|
|
21006
|
+
ok: true,
|
|
21007
|
+
sessionID,
|
|
21008
|
+
directory: plan.directory,
|
|
21009
|
+
worktree: await worktreeForIssue(ctx, active.root, issue2.number),
|
|
21010
|
+
draft: fillTemplate(ctx.config.draftPromptIssue, { number: issue2.number })
|
|
21011
|
+
};
|
|
21012
|
+
} catch (error62) {
|
|
21013
|
+
return { ok: false, error: failure2("session", error62) };
|
|
21014
|
+
}
|
|
21015
|
+
},
|
|
21016
|
+
async worktreeCleanup(input2) {
|
|
21017
|
+
const ctx = await getContext();
|
|
21018
|
+
const { active } = await resolveActive({ ctx, repo: input2.repo });
|
|
21019
|
+
if (!active) {
|
|
21020
|
+
return { removed: false, reason: "unknown", message: "Repo was not found." };
|
|
21021
|
+
}
|
|
21022
|
+
const directory = input2.directory ?? (input2.number !== void 0 ? issueWorktreePath(active.root, input2.number) : null);
|
|
21023
|
+
if (!directory) {
|
|
21024
|
+
return {
|
|
21025
|
+
removed: false,
|
|
21026
|
+
reason: "unknown",
|
|
21027
|
+
message: "Pass number or directory."
|
|
21028
|
+
};
|
|
21029
|
+
}
|
|
21030
|
+
if (!isInsideWorktrees(active.root, directory)) {
|
|
21031
|
+
return {
|
|
21032
|
+
removed: false,
|
|
21033
|
+
reason: "outside",
|
|
21034
|
+
message: "Only worktrees under .worktrees/ can be cleaned."
|
|
21035
|
+
};
|
|
21036
|
+
}
|
|
21037
|
+
if (input2.inUse) {
|
|
21038
|
+
return { removed: false, reason: "in_use", message: "A tab for this worktree is open." };
|
|
21039
|
+
}
|
|
21040
|
+
const result = await removeWorktree({
|
|
21041
|
+
repoRoot: active.root,
|
|
21042
|
+
directory,
|
|
21043
|
+
runner: ctx.runner
|
|
21044
|
+
});
|
|
21045
|
+
if (result.removed) emitChanged(ctx, active.repo);
|
|
21046
|
+
return {
|
|
21047
|
+
removed: result.removed,
|
|
21048
|
+
reason: result.removed ? "removed" : result.reason ?? "unknown",
|
|
21049
|
+
message: result.reason === "dirty" ? "Uncommitted changes; the worktree cannot be cleaned." : result.reason === "missing" ? "The worktree was not found." : null
|
|
21050
|
+
};
|
|
21051
|
+
},
|
|
21052
|
+
async worktreeOrphans(input2) {
|
|
21053
|
+
const ctx = await getContext();
|
|
21054
|
+
const { active } = await resolveActive({ ctx, repo: input2?.repo });
|
|
21055
|
+
if (!active) {
|
|
21056
|
+
return { orphans: [], error: failure2("repo", "Repo was not found.") };
|
|
21057
|
+
}
|
|
21058
|
+
const result = await loadIssuesResult({
|
|
21059
|
+
paths: ctx.paths,
|
|
21060
|
+
repo: repoRef(active),
|
|
21061
|
+
config: ctx.config,
|
|
21062
|
+
runner: ctx.runner,
|
|
21063
|
+
cwd: active.root,
|
|
21064
|
+
now: ctx.now
|
|
21065
|
+
});
|
|
21066
|
+
if (result.error && !result.cached) {
|
|
21067
|
+
return { orphans: [], error: failure2("gh", result.error.message) };
|
|
21068
|
+
}
|
|
21069
|
+
const worktrees = await listIssueWorktrees({
|
|
21070
|
+
repoRoot: active.root,
|
|
21071
|
+
runner: ctx.runner
|
|
21072
|
+
});
|
|
21073
|
+
const orphans = selectOrphanWorktrees(worktrees, new Set(result.issues.map((issue2) => issue2.number)));
|
|
21074
|
+
const views = [];
|
|
21075
|
+
for (const orphan of orphans) views.push(await toWorktreeView(ctx, orphan));
|
|
21076
|
+
return {
|
|
21077
|
+
orphans: views,
|
|
21078
|
+
error: result.error ? failure2("gh", result.error.message) : null
|
|
21079
|
+
};
|
|
21080
|
+
},
|
|
21081
|
+
async sessionResume(input2) {
|
|
21082
|
+
const ctx = await getContext();
|
|
21083
|
+
const { active } = await resolveActive({ ctx, repo: input2.repo });
|
|
21084
|
+
if (!active) return { ok: false, error: failure2("repo", "Repo was not found.") };
|
|
21085
|
+
let issue2;
|
|
21086
|
+
try {
|
|
21087
|
+
const result = await loadIssuesResult({
|
|
21088
|
+
paths: ctx.paths,
|
|
21089
|
+
repo: repoRef(active),
|
|
21090
|
+
config: ctx.config,
|
|
21091
|
+
runner: ctx.runner,
|
|
21092
|
+
cwd: active.root,
|
|
21093
|
+
now: ctx.now
|
|
21094
|
+
});
|
|
21095
|
+
issue2 = result.issues.find((candidate) => candidate.number === input2.number);
|
|
21096
|
+
} catch {
|
|
21097
|
+
issue2 = void 0;
|
|
21098
|
+
}
|
|
21099
|
+
const draft = fillTemplate(ctx.config.draftPromptIssue, { number: input2.number });
|
|
21100
|
+
if (!issue2) {
|
|
21101
|
+
return {
|
|
21102
|
+
ok: false,
|
|
21103
|
+
error: failure2("issue", `Issue #${input2.number} was not found.`)
|
|
21104
|
+
};
|
|
21105
|
+
}
|
|
21106
|
+
const worktree = await worktreeForIssue(ctx, active.root, input2.number);
|
|
21107
|
+
return {
|
|
21108
|
+
ok: true,
|
|
21109
|
+
worktree,
|
|
21110
|
+
draft,
|
|
21111
|
+
existing: worktree.registered && worktree.exists
|
|
21112
|
+
};
|
|
21113
|
+
}
|
|
21114
|
+
};
|
|
21115
|
+
}
|
|
21116
|
+
function repoRef(context) {
|
|
21117
|
+
if (context.info) return context.info.ref;
|
|
21118
|
+
const [owner = "unknown", name = context.repo] = context.repo.split("/");
|
|
21119
|
+
return { owner, name };
|
|
21120
|
+
}
|
|
19832
21121
|
|
|
19833
21122
|
// packages/plugin/src/status.ts
|
|
19834
21123
|
function sessionIDOf(event) {
|
|
@@ -19909,103 +21198,151 @@ var KEY = process.env.SKILLS_DECK_KEY;
|
|
|
19909
21198
|
var REPO = process.env.SKILLS_DECK_REPO ?? "";
|
|
19910
21199
|
var KIND = process.env.SKILLS_DECK_KIND === "dialogue" ? "dialogue" : "issue";
|
|
19911
21200
|
var PREVIEW_FLUSH_MS = 400;
|
|
21201
|
+
function setupChildSession(ctx) {
|
|
21202
|
+
const paths = resolvePaths();
|
|
21203
|
+
const sessionsDir = sessionsDirFor(paths, REPO || null);
|
|
21204
|
+
const stamp = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
21205
|
+
const patch = (update) => {
|
|
21206
|
+
const current = readSession(sessionsDir, KEY);
|
|
21207
|
+
if (!current) return;
|
|
21208
|
+
writeSession(sessionsDir, mergeRecord(current, update, process.pid, stamp()));
|
|
21209
|
+
};
|
|
21210
|
+
const initial = readSession(sessionsDir, KEY);
|
|
21211
|
+
const record2 = initial ?? {
|
|
21212
|
+
key: KEY,
|
|
21213
|
+
kind: KIND,
|
|
21214
|
+
repo: REPO,
|
|
21215
|
+
label: KEY,
|
|
21216
|
+
pid: process.pid,
|
|
21217
|
+
status: "input_wait",
|
|
21218
|
+
reason: "idle",
|
|
21219
|
+
createdAt: stamp(),
|
|
21220
|
+
updatedAt: stamp()
|
|
21221
|
+
};
|
|
21222
|
+
writeSession(sessionsDir, {
|
|
21223
|
+
...record2,
|
|
21224
|
+
pid: process.pid,
|
|
21225
|
+
status: "input_wait",
|
|
21226
|
+
reason: "idle",
|
|
21227
|
+
updatedAt: stamp()
|
|
21228
|
+
});
|
|
21229
|
+
let boundSessionID = record2.opencodeSessionID;
|
|
21230
|
+
let preview = record2.preview ?? "";
|
|
21231
|
+
let previewDirty = false;
|
|
21232
|
+
let flushTimer;
|
|
21233
|
+
const flushPreview = () => {
|
|
21234
|
+
if (!previewDirty) return;
|
|
21235
|
+
previewDirty = false;
|
|
21236
|
+
patch({ preview });
|
|
21237
|
+
};
|
|
21238
|
+
const scheduleFlush = () => {
|
|
21239
|
+
if (flushTimer) return;
|
|
21240
|
+
flushTimer = setTimeout(() => {
|
|
21241
|
+
flushTimer = void 0;
|
|
21242
|
+
flushPreview();
|
|
21243
|
+
}, PREVIEW_FLUSH_MS);
|
|
21244
|
+
flushTimer.unref?.();
|
|
21245
|
+
};
|
|
21246
|
+
const controller = new AbortController();
|
|
21247
|
+
const onExit = () => {
|
|
21248
|
+
try {
|
|
21249
|
+
flushPreview();
|
|
21250
|
+
patch({ status: "exited", exitedAt: stamp() });
|
|
21251
|
+
} catch {
|
|
21252
|
+
}
|
|
21253
|
+
};
|
|
21254
|
+
process.once("exit", onExit);
|
|
21255
|
+
void (async () => {
|
|
21256
|
+
try {
|
|
21257
|
+
for await (const event of ctx.event.subscribe({ signal: controller.signal })) {
|
|
21258
|
+
const typed = event;
|
|
21259
|
+
const sessionID = sessionIDOf(typed);
|
|
21260
|
+
if (!boundSessionID && sessionID) {
|
|
21261
|
+
boundSessionID = sessionID;
|
|
21262
|
+
patch({ opencodeSessionID: sessionID });
|
|
21263
|
+
} else if (boundSessionID && sessionID && sessionID !== boundSessionID) {
|
|
21264
|
+
continue;
|
|
21265
|
+
}
|
|
21266
|
+
const update = mapEvent(typed);
|
|
21267
|
+
if (update) {
|
|
21268
|
+
if (update.status === "exited") {
|
|
21269
|
+
preview = "";
|
|
21270
|
+
previewDirty = false;
|
|
21271
|
+
if (flushTimer) clearTimeout(flushTimer);
|
|
21272
|
+
flushTimer = void 0;
|
|
21273
|
+
patch({ ...update, preview: "" });
|
|
21274
|
+
} else if (typed.type === "session.execution.started") {
|
|
21275
|
+
preview = "";
|
|
21276
|
+
previewDirty = false;
|
|
21277
|
+
patch({ ...update, preview: "" });
|
|
21278
|
+
} else {
|
|
21279
|
+
patch(update);
|
|
21280
|
+
}
|
|
21281
|
+
}
|
|
21282
|
+
const delta = textDeltaOf(typed);
|
|
21283
|
+
if (delta) {
|
|
21284
|
+
preview = appendPreview(preview, delta);
|
|
21285
|
+
previewDirty = true;
|
|
21286
|
+
scheduleFlush();
|
|
21287
|
+
}
|
|
21288
|
+
}
|
|
21289
|
+
} catch {
|
|
21290
|
+
}
|
|
21291
|
+
})();
|
|
21292
|
+
return () => {
|
|
21293
|
+
controller.abort();
|
|
21294
|
+
process.off("exit", onExit);
|
|
21295
|
+
};
|
|
21296
|
+
}
|
|
19912
21297
|
var index_default = Plugin.define({
|
|
19913
21298
|
id: "opencode-skills-deck",
|
|
19914
21299
|
async setup(ctx) {
|
|
19915
|
-
if (!KEY) return;
|
|
19916
21300
|
const paths = resolvePaths();
|
|
19917
|
-
const
|
|
19918
|
-
const
|
|
19919
|
-
const
|
|
19920
|
-
|
|
19921
|
-
|
|
19922
|
-
writeSession(sessionsDir, mergeRecord(current, update, process.pid, stamp()));
|
|
19923
|
-
};
|
|
19924
|
-
const initial = readSession(sessionsDir, KEY);
|
|
19925
|
-
const record2 = initial ?? {
|
|
19926
|
-
key: KEY,
|
|
19927
|
-
kind: KIND,
|
|
19928
|
-
repo: REPO,
|
|
19929
|
-
label: KEY,
|
|
19930
|
-
pid: process.pid,
|
|
19931
|
-
status: "input_wait",
|
|
19932
|
-
reason: "idle",
|
|
19933
|
-
createdAt: stamp(),
|
|
19934
|
-
updatedAt: stamp()
|
|
19935
|
-
};
|
|
19936
|
-
writeSession(sessionsDir, {
|
|
19937
|
-
...record2,
|
|
19938
|
-
pid: process.pid,
|
|
19939
|
-
status: "input_wait",
|
|
19940
|
-
reason: "idle",
|
|
19941
|
-
updatedAt: stamp()
|
|
19942
|
-
});
|
|
19943
|
-
let boundSessionID = record2.opencodeSessionID;
|
|
19944
|
-
let preview = record2.preview ?? "";
|
|
19945
|
-
let previewDirty = false;
|
|
19946
|
-
let flushTimer;
|
|
19947
|
-
const flushPreview = () => {
|
|
19948
|
-
if (!previewDirty) return;
|
|
19949
|
-
previewDirty = false;
|
|
19950
|
-
patch({ preview });
|
|
19951
|
-
};
|
|
19952
|
-
const scheduleFlush = () => {
|
|
19953
|
-
if (flushTimer) return;
|
|
19954
|
-
flushTimer = setTimeout(() => {
|
|
19955
|
-
flushTimer = void 0;
|
|
19956
|
-
flushPreview();
|
|
19957
|
-
}, PREVIEW_FLUSH_MS);
|
|
19958
|
-
flushTimer.unref?.();
|
|
19959
|
-
};
|
|
19960
|
-
const controller = new AbortController();
|
|
19961
|
-
const onExit = () => {
|
|
21301
|
+
const config2 = loadConfig(paths);
|
|
21302
|
+
const sourceDir = fileURLToPath(new URL("../resources/skills", import.meta.url));
|
|
21303
|
+
const available = bundledSkillNames(sourceDir);
|
|
21304
|
+
let installed = available;
|
|
21305
|
+
if (config2.installSkillsOnStart) {
|
|
19962
21306
|
try {
|
|
19963
|
-
|
|
19964
|
-
|
|
21307
|
+
const result = installBundledSkills({
|
|
21308
|
+
sourceDir,
|
|
21309
|
+
targetDir: opencodeSkillsDir()
|
|
21310
|
+
});
|
|
21311
|
+
installed = [
|
|
21312
|
+
.../* @__PURE__ */ new Set([...result.installed, ...result.updated, ...result.unchanged, ...result.skipped])
|
|
21313
|
+
];
|
|
19965
21314
|
} catch {
|
|
19966
|
-
|
|
19967
|
-
|
|
19968
|
-
|
|
19969
|
-
|
|
19970
|
-
|
|
19971
|
-
|
|
19972
|
-
|
|
19973
|
-
|
|
19974
|
-
|
|
19975
|
-
|
|
19976
|
-
|
|
19977
|
-
|
|
19978
|
-
|
|
19979
|
-
|
|
19980
|
-
|
|
19981
|
-
|
|
19982
|
-
|
|
19983
|
-
|
|
19984
|
-
|
|
19985
|
-
|
|
19986
|
-
|
|
19987
|
-
|
|
19988
|
-
|
|
19989
|
-
|
|
19990
|
-
previewDirty = false;
|
|
19991
|
-
patch({ ...update, preview: "" });
|
|
19992
|
-
} else {
|
|
19993
|
-
patch(update);
|
|
19994
|
-
}
|
|
19995
|
-
}
|
|
19996
|
-
const delta = textDeltaOf(typed);
|
|
19997
|
-
if (delta) {
|
|
19998
|
-
preview = appendPreview(preview, delta);
|
|
19999
|
-
previewDirty = true;
|
|
20000
|
-
scheduleFlush();
|
|
20001
|
-
}
|
|
21315
|
+
installed = [];
|
|
21316
|
+
}
|
|
21317
|
+
}
|
|
21318
|
+
let registration;
|
|
21319
|
+
registration = await ctx.rpc.register(
|
|
21320
|
+
ControlTower,
|
|
21321
|
+
createControlTowerHandlers(async () => ({
|
|
21322
|
+
directory: ctx.location.directory,
|
|
21323
|
+
config: config2,
|
|
21324
|
+
paths,
|
|
21325
|
+
runner: createRunner(),
|
|
21326
|
+
now: Date.now(),
|
|
21327
|
+
appVersion: ctx.app.version ?? null,
|
|
21328
|
+
skills: { available, installed },
|
|
21329
|
+
sessionsIn: async (directory) => {
|
|
21330
|
+
const response = await ctx.session.list({ directory });
|
|
21331
|
+
return response.data.length;
|
|
21332
|
+
},
|
|
21333
|
+
createSession: async (directory) => {
|
|
21334
|
+
const info = await ctx.session.create({ location: { directory } });
|
|
21335
|
+
return info.id;
|
|
21336
|
+
},
|
|
21337
|
+
emit: (event, data) => {
|
|
21338
|
+
void registration?.events.emit(event, data);
|
|
20002
21339
|
}
|
|
20003
|
-
}
|
|
20004
|
-
|
|
20005
|
-
|
|
21340
|
+
}))
|
|
21341
|
+
);
|
|
21342
|
+
const childCleanup = KEY ? setupChildSession(ctx) : void 0;
|
|
20006
21343
|
return () => {
|
|
20007
|
-
|
|
20008
|
-
|
|
21344
|
+
childCleanup?.();
|
|
21345
|
+
void registration?.dispose();
|
|
20009
21346
|
};
|
|
20010
21347
|
}
|
|
20011
21348
|
});
|