opencode-skills-deck 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tui.js CHANGED
@@ -19790,16 +19790,36 @@ function normalizeOpenKey(value) {
19790
19790
  // packages/core/src/exec.ts
19791
19791
  import { spawn } from "node:child_process";
19792
19792
  var MAX_BUFFER = 16 * 1024 * 1024;
19793
- function createRunner() {
19794
- return (command, args, options) => new Promise((resolve2, reject) => {
19793
+ var DEFAULT_TIMEOUT_MS = 3e4;
19794
+ function createRunner(options = {}) {
19795
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
19796
+ return (command, args, options2) => new Promise((resolve2, reject) => {
19795
19797
  const child = spawn(command, args, {
19796
- cwd: options?.cwd,
19798
+ cwd: options2?.cwd,
19797
19799
  env: process.env,
19798
19800
  stdio: ["ignore", "pipe", "pipe"]
19799
19801
  });
19800
19802
  let stdout = "";
19801
19803
  let stderr = "";
19802
19804
  let overflow = false;
19805
+ let settled = false;
19806
+ const fail = (error62) => {
19807
+ if (settled) return;
19808
+ settled = true;
19809
+ clearTimeout(timer);
19810
+ reject(error62);
19811
+ };
19812
+ const succeed = (result) => {
19813
+ if (settled) return;
19814
+ settled = true;
19815
+ clearTimeout(timer);
19816
+ resolve2(result);
19817
+ };
19818
+ const timer = setTimeout(() => {
19819
+ child.kill("SIGTERM");
19820
+ fail(new Error(`${command} ${args.join(" ")} timed out after ${timeoutMs}ms`));
19821
+ }, timeoutMs);
19822
+ timer.unref?.();
19803
19823
  child.stdout?.setEncoding("utf8");
19804
19824
  child.stderr?.setEncoding("utf8");
19805
19825
  child.stdout?.on("data", (chunk) => {
@@ -19819,67 +19839,78 @@ function createRunner() {
19819
19839
  }
19820
19840
  });
19821
19841
  child.on("error", (error62) => {
19822
- reject(new Error(`${command} ${args.join(" ")} failed: ${error62.message}`));
19842
+ fail(new Error(`${command} ${args.join(" ")} failed: ${error62.message}`));
19823
19843
  });
19824
19844
  child.on("close", (code) => {
19845
+ if (settled) return;
19825
19846
  if (overflow) {
19826
- reject(new Error(`${command} ${args.join(" ")} failed: \u51FA\u529B\u304C\u5927\u304D\u3059\u304E\u307E\u3059`));
19847
+ fail(new Error(`${command} ${args.join(" ")} failed: \u51FA\u529B\u304C\u5927\u304D\u3059\u304E\u307E\u3059`));
19827
19848
  return;
19828
19849
  }
19829
19850
  if (code === 0) {
19830
- resolve2({ stdout, stderr });
19851
+ succeed({ stdout, stderr });
19831
19852
  return;
19832
19853
  }
19833
19854
  const detail = stderr.trim() || `exit code ${code ?? "unknown"}`;
19834
- reject(new Error(`${command} ${args.join(" ")} failed: ${detail}`));
19855
+ fail(new Error(`${command} ${args.join(" ")} failed: ${detail}`));
19835
19856
  });
19836
19857
  });
19837
19858
  }
19838
19859
 
19839
19860
  // packages/core/src/paths.ts
19840
19861
  import { homedir } from "node:os";
19841
- import { join as join2 } from "node:path";
19862
+ import { posix, win32 } from "node:path";
19842
19863
  var APP_DIR = "opencode-skills-deck";
19864
+ function pathFor(platform) {
19865
+ return platform === "win32" ? win32 : posix;
19866
+ }
19867
+ function styleOf(path) {
19868
+ return /^[A-Za-z]:[\\/]/.test(path) || path.startsWith("\\\\") ? "win32" : "linux";
19869
+ }
19843
19870
  function configRootOf(platform, env, home) {
19844
- return env.XDG_CONFIG_HOME ?? (platform === "win32" ? env.APPDATA ?? join2(home, "AppData", "Roaming") : join2(home, ".config"));
19871
+ const path = pathFor(platform);
19872
+ return env.XDG_CONFIG_HOME ?? (platform === "win32" ? env.APPDATA ?? path.join(home, "AppData", "Roaming") : path.join(home, ".config"));
19845
19873
  }
19846
19874
  function stateRootOf(platform, env, home) {
19847
- return env.XDG_STATE_HOME ?? (platform === "win32" ? env.LOCALAPPDATA ?? join2(home, "AppData", "Local") : join2(home, ".local", "state"));
19875
+ const path = pathFor(platform);
19876
+ return env.XDG_STATE_HOME ?? (platform === "win32" ? env.LOCALAPPDATA ?? path.join(home, "AppData", "Local") : path.join(home, ".local", "state"));
19848
19877
  }
19849
19878
  function opencodeConfigDir(options = {}) {
19850
19879
  const platform = options.platform ?? process.platform;
19851
19880
  const env = options.env ?? process.env;
19852
19881
  const home = options.home ?? homedir();
19853
- return join2(configRootOf(platform, env, home), "opencode");
19882
+ return pathFor(platform).join(configRootOf(platform, env, home), "opencode");
19854
19883
  }
19855
19884
  function opencodeSkillsDir(options = {}) {
19856
- return join2(opencodeConfigDir(options), "skills");
19885
+ return pathFor(options.platform ?? process.platform).join(opencodeConfigDir(options), "skills");
19857
19886
  }
19858
19887
  function repoSessionSlug(repo) {
19859
19888
  return repo.replace(/[^a-zA-Z0-9._-]+/g, "__");
19860
19889
  }
19861
19890
  function sessionsDirFor(paths, repo) {
19862
- return repo ? join2(paths.sessionsDir, repoSessionSlug(repo)) : paths.sessionsDir;
19891
+ if (!repo) return paths.sessionsDir;
19892
+ return pathFor(styleOf(paths.sessionsDir)).join(paths.sessionsDir, repoSessionSlug(repo));
19863
19893
  }
19864
19894
  function resolvePaths(options = {}) {
19865
19895
  const platform = options.platform ?? process.platform;
19866
19896
  const env = options.env ?? process.env;
19867
19897
  const home = options.home ?? homedir();
19898
+ const path = pathFor(platform);
19868
19899
  const configRoot = configRootOf(platform, env, home);
19869
19900
  const stateRoot = stateRootOf(platform, env, home);
19870
- const configDir = join2(configRoot, APP_DIR);
19871
- const stateDir = join2(stateRoot, APP_DIR);
19901
+ const configDir = path.join(configRoot, APP_DIR);
19902
+ const stateDir = path.join(stateRoot, APP_DIR);
19872
19903
  return {
19873
19904
  configDir,
19874
19905
  stateDir,
19875
- sessionsDir: join2(stateDir, "sessions"),
19876
- issueCacheDir: join2(stateDir, "issues")
19906
+ sessionsDir: path.join(stateDir, "sessions"),
19907
+ issueCacheDir: path.join(stateDir, "issues")
19877
19908
  };
19878
19909
  }
19879
19910
 
19880
19911
  // packages/core/src/repo.ts
19881
19912
  import { execFileSync } from "node:child_process";
19882
- import { join as join3 } from "node:path";
19913
+ import { join as join2, posix as posix2, win32 as win322 } from "node:path";
19883
19914
  var REMOTE_PATTERNS = [
19884
19915
  /^git@[^:]+:(?<owner>[^/]+)\/(?<name>[^/]+?)(?:\.git)?$/,
19885
19916
  /^(?:ssh|git|https?):\/\/(?:[^@/]+@)?[^/]+\/(?<owner>[^/]+)\/(?<name>[^/]+?)(?:\.git)?\/?$/
@@ -19904,7 +19935,7 @@ function branchName(issueNumber, title) {
19904
19935
  return slug ? `issue/${issueNumber}-${slug}` : `issue/${issueNumber}`;
19905
19936
  }
19906
19937
  function issueWorktreePath(repoRoot, issueNumber) {
19907
- return join3(repoRoot, ".worktrees", `issue-${issueNumber}`);
19938
+ return join2(repoRoot, ".worktrees", `issue-${issueNumber}`);
19908
19939
  }
19909
19940
  function issueKey(issueNumber) {
19910
19941
  return `issue-${issueNumber}`;
@@ -19960,10 +19991,10 @@ import {
19960
19991
  watch,
19961
19992
  writeFileSync
19962
19993
  } from "node:fs";
19963
- import { join as join4 } from "node:path";
19994
+ import { join as join3 } from "node:path";
19964
19995
  var STARTUP_GRACE_MS = 2e4;
19965
19996
  function sessionFilePath(dir, key) {
19966
- return join4(dir, `${key}.json`);
19997
+ return join3(dir, `${key}.json`);
19967
19998
  }
19968
19999
  function readSessions(dir) {
19969
20000
  if (!existsSync(dir)) return [];
@@ -20090,7 +20121,7 @@ function migrateLegacySessions(input2) {
20090
20121
  // packages/core/src/terminal.ts
20091
20122
  import { spawn as spawn2 } from "node:child_process";
20092
20123
  import { existsSync as existsSync2 } from "node:fs";
20093
- import { delimiter, join as join5 } from "node:path";
20124
+ import { delimiter, join as join4 } from "node:path";
20094
20125
  var DARWIN_BINARIES = {
20095
20126
  alacritty: "/Applications/Alacritty.app/Contents/MacOS/alacritty",
20096
20127
  kitty: "/Applications/kitty.app/Contents/MacOS/kitty"
@@ -20099,7 +20130,7 @@ function whichInPath(command, env) {
20099
20130
  const pathValue = env.PATH ?? "";
20100
20131
  for (const directory of pathValue.split(delimiter)) {
20101
20132
  if (!directory) continue;
20102
- const candidate = join5(directory, command);
20133
+ const candidate = join4(directory, command);
20103
20134
  if (existsSync2(candidate)) return candidate;
20104
20135
  }
20105
20136
  return null;
@@ -20174,8 +20205,8 @@ function launchInTerminal(input2) {
20174
20205
  }
20175
20206
 
20176
20207
  // packages/core/src/worktree.ts
20177
- import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
20178
- import { join as join6, resolve, sep } from "node:path";
20208
+ import { existsSync as existsSync3, readFileSync as readFileSync3, realpathSync, writeFileSync as writeFileSync2 } from "node:fs";
20209
+ import { join as join5, resolve, sep } from "node:path";
20179
20210
  async function defaultBranch(input2) {
20180
20211
  if (input2.configured) return input2.configured;
20181
20212
  try {
@@ -20192,22 +20223,54 @@ async function ensureWorktreesExcluded(input2) {
20192
20223
  const { stdout } = await input2.runner("git", ["rev-parse", "--git-path", "info/exclude"], {
20193
20224
  cwd: input2.repoRoot
20194
20225
  });
20195
- const path = join6(input2.repoRoot, stdout.trim());
20226
+ const path = join5(input2.repoRoot, stdout.trim());
20196
20227
  const existing = existsSync3(path) ? readFileSync3(path, "utf8") : "";
20197
20228
  if (existing.split("\n").some((line) => line.trim() === ".worktrees/")) return;
20198
20229
  const suffix = existing.endsWith("\n") || existing === "" ? "" : "\n";
20199
20230
  writeFileSync2(path, `${existing}${suffix}.worktrees/
20200
20231
  `);
20201
20232
  }
20233
+ async function readWorktreeEntries(input2) {
20234
+ const { stdout } = await input2.runner("git", ["worktree", "list", "--porcelain"], {
20235
+ cwd: input2.repoRoot
20236
+ });
20237
+ const entries = [];
20238
+ let current = null;
20239
+ for (const line of stdout.split("\n")) {
20240
+ if (line.startsWith("worktree ")) {
20241
+ if (current) entries.push(current);
20242
+ current = {
20243
+ directory: line.slice("worktree ".length).trim(),
20244
+ branch: null,
20245
+ detached: false,
20246
+ bare: false
20247
+ };
20248
+ continue;
20249
+ }
20250
+ if (!current) continue;
20251
+ if (line.startsWith("branch ")) {
20252
+ current.branch = line.slice("branch ".length).replace(/^refs\/heads\//, "").trim();
20253
+ } else if (line === "detached") {
20254
+ current.detached = true;
20255
+ } else if (line === "bare") {
20256
+ current.bare = true;
20257
+ }
20258
+ }
20259
+ if (current) entries.push(current);
20260
+ return entries;
20261
+ }
20202
20262
  async function registeredWorktrees(input2) {
20203
- const { stdout } = await input2.runner("git", ["worktree", "list", "--porcelain"], { cwd: input2.repoRoot });
20204
- return stdout.split("\n").filter((line) => line.startsWith("worktree ")).map((line) => line.slice("worktree ".length).trim());
20263
+ return (await readWorktreeEntries(input2)).map((entry) => entry.directory);
20205
20264
  }
20206
20265
  function normalized(path) {
20207
- return resolve(path).replace(/\/+$/, "");
20266
+ try {
20267
+ return realpathSync(path).replace(/\/+$/, "");
20268
+ } catch {
20269
+ return resolve(path).replace(/\/+$/, "");
20270
+ }
20208
20271
  }
20209
20272
  async function findOrphanWorktrees(input2) {
20210
- const root = normalized(join6(input2.repoRoot, ".worktrees"));
20273
+ const root = normalized(join5(input2.repoRoot, ".worktrees"));
20211
20274
  const known = new Set(
20212
20275
  input2.records.map((record2) => record2.worktree).filter((value) => Boolean(value)).map(normalized)
20213
20276
  );
@@ -20372,8 +20435,8 @@ function dispatchDialogue(input2) {
20372
20435
  }
20373
20436
 
20374
20437
  // packages/core/src/issue.ts
20375
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
20376
- import { dirname, join as join7 } from "node:path";
20438
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
20439
+ import { dirname, join as join6 } from "node:path";
20377
20440
  var SUMMARY_HEADING = /summary|要約|概要/i;
20378
20441
  var CRITERIA_HEADING = /acceptance criteria|受け入れ基準|完了条件|acceptance/i;
20379
20442
  var BULLET = /^\s*(?:[-*+]|\d+\.)\s+(?:\[[ xX]\]\s*)?(.+)$/;
@@ -20431,42 +20494,103 @@ async function fetchIssues(input2) {
20431
20494
  return rows.map(toIssue).sort((left, right) => left.number - right.number);
20432
20495
  }
20433
20496
  function issueCachePath(paths, repo) {
20434
- return join7(paths.issueCacheDir, `${repo.owner}__${repo.name}.json`);
20497
+ return join6(paths.issueCacheDir, `${repo.owner}__${repo.name}.json`);
20498
+ }
20499
+ function normalizeCachedIssue(value) {
20500
+ if (!value || typeof value !== "object") return null;
20501
+ const issue2 = value;
20502
+ if (typeof issue2.number !== "number" || typeof issue2.title !== "string") return null;
20503
+ return {
20504
+ number: issue2.number,
20505
+ title: issue2.title,
20506
+ labels: Array.isArray(issue2.labels) ? issue2.labels.filter((label) => typeof label === "string") : [],
20507
+ state: typeof issue2.state === "string" ? issue2.state : "OPEN",
20508
+ updatedAt: typeof issue2.updatedAt === "string" ? issue2.updatedAt : (/* @__PURE__ */ new Date(0)).toISOString(),
20509
+ summary: typeof issue2.summary === "string" ? issue2.summary : "",
20510
+ criteria: Array.isArray(issue2.criteria) ? issue2.criteria.filter((criterion) => typeof criterion === "string") : []
20511
+ };
20435
20512
  }
20436
20513
  function readIssueCache(path) {
20437
20514
  try {
20438
20515
  const parsed = JSON.parse(readFileSync4(path, "utf8"));
20439
- return Array.isArray(parsed.issues) ? parsed : null;
20516
+ if (!Array.isArray(parsed.issues) || typeof parsed.fetchedAt !== "number") {
20517
+ rmSync2(path, { force: true });
20518
+ return null;
20519
+ }
20520
+ const issues = parsed.issues.map(normalizeCachedIssue);
20521
+ if (issues.some((issue2) => issue2 === null)) {
20522
+ rmSync2(path, { force: true });
20523
+ return null;
20524
+ }
20525
+ return { fetchedAt: parsed.fetchedAt, issues };
20440
20526
  } catch {
20527
+ try {
20528
+ rmSync2(path, { force: true });
20529
+ } catch {
20530
+ }
20441
20531
  return null;
20442
20532
  }
20443
20533
  }
20444
20534
  function writeIssueCache(path, cache) {
20445
20535
  mkdirSync2(dirname(path), { recursive: true });
20446
- writeFileSync3(path, JSON.stringify(cache, null, 2));
20536
+ const temporary = `${path}.${process.pid}.${Date.now()}.tmp`;
20537
+ writeFileSync3(temporary, `${JSON.stringify(cache, null, 2)}
20538
+ `);
20539
+ renameSync2(temporary, path);
20447
20540
  }
20448
20541
  var CACHE_TTL_MS = 6e4;
20449
- async function loadIssues(input2) {
20542
+ function classifyGhError(error62) {
20543
+ const message = error62 instanceof Error ? error62.message : String(error62);
20544
+ if (/not authenticated|authentication|401|gh auth login/i.test(message)) {
20545
+ return { code: "auth", message };
20546
+ }
20547
+ if (/rate limit|secondary rate|403/i.test(message)) {
20548
+ return { code: "rate_limit", message };
20549
+ }
20550
+ if (/ENOTFOUND|ECONNREFUSED|ETIMEDOUT|fetch failed|network/i.test(message)) {
20551
+ return { code: "network", message };
20552
+ }
20553
+ if (/^gh\b|gh issue/i.test(message)) return { code: "gh", message };
20554
+ return { code: "unknown", message };
20555
+ }
20556
+ async function loadIssuesResult(input2) {
20450
20557
  const path = issueCachePath(input2.paths, input2.repo);
20451
20558
  const now = input2.now ?? Date.now();
20452
- if (!input2.force) {
20453
- const cache = readIssueCache(path);
20454
- if (cache && now - cache.fetchedAt < CACHE_TTL_MS) return cache.issues;
20559
+ const cache = readIssueCache(path);
20560
+ if (!input2.force && cache && now - cache.fetchedAt < CACHE_TTL_MS) {
20561
+ return { issues: cache.issues, cached: true, stale: false, fetchedAt: cache.fetchedAt, error: null };
20455
20562
  }
20456
- const issues = await fetchIssues({
20457
- repo: input2.repo,
20458
- config: input2.config,
20459
- runner: input2.runner,
20460
- cwd: input2.cwd
20461
- });
20462
- writeIssueCache(path, { fetchedAt: now, issues });
20463
- return issues;
20563
+ try {
20564
+ const issues = await fetchIssues({
20565
+ repo: input2.repo,
20566
+ config: input2.config,
20567
+ runner: input2.runner,
20568
+ cwd: input2.cwd
20569
+ });
20570
+ writeIssueCache(path, { fetchedAt: now, issues });
20571
+ return { issues, cached: false, stale: false, fetchedAt: now, error: null };
20572
+ } catch (error62) {
20573
+ const failure2 = classifyGhError(error62);
20574
+ if (cache) {
20575
+ return {
20576
+ issues: cache.issues,
20577
+ cached: true,
20578
+ stale: true,
20579
+ fetchedAt: cache.fetchedAt,
20580
+ error: failure2
20581
+ };
20582
+ }
20583
+ return { issues: [], cached: false, stale: false, fetchedAt: null, error: failure2 };
20584
+ }
20585
+ }
20586
+ async function loadIssues(input2) {
20587
+ return (await loadIssuesResult(input2)).issues;
20464
20588
  }
20465
20589
 
20466
20590
  // packages/core/src/skills.ts
20467
20591
  import { createHash } from "node:crypto";
20468
- import { cpSync, existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "node:fs";
20469
- import { join as join8, relative, sep as sep2 } from "node:path";
20592
+ import { cpSync, existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync5, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "node:fs";
20593
+ import { join as join7, relative, sep as sep2 } from "node:path";
20470
20594
  var MANIFEST_NAME = ".skills-deck.json";
20471
20595
  function hashSkillDir(dir) {
20472
20596
  const hash2 = createHash("sha256");
@@ -20475,7 +20599,7 @@ function hashSkillDir(dir) {
20475
20599
  (left, right) => left.name.localeCompare(right.name)
20476
20600
  );
20477
20601
  for (const entry of entries) {
20478
- const absolute = join8(current, entry.name);
20602
+ const absolute = join7(current, entry.name);
20479
20603
  const key = relative(dir, absolute).split(sep2).join("/");
20480
20604
  if (entry.isDirectory()) {
20481
20605
  hash2.update(`d:${key}
@@ -20494,7 +20618,7 @@ function hashSkillDir(dir) {
20494
20618
  }
20495
20619
  function readManifest(targetDir) {
20496
20620
  try {
20497
- const parsed = JSON.parse(readFileSync5(join8(targetDir, MANIFEST_NAME), "utf8"));
20621
+ const parsed = JSON.parse(readFileSync5(join7(targetDir, MANIFEST_NAME), "utf8"));
20498
20622
  if (!parsed || typeof parsed.version !== "string" || typeof parsed.skills !== "object") return null;
20499
20623
  return parsed;
20500
20624
  } catch {
@@ -20502,19 +20626,19 @@ function readManifest(targetDir) {
20502
20626
  }
20503
20627
  }
20504
20628
  function writeManifest(targetDir, manifest) {
20505
- writeFileSync4(join8(targetDir, MANIFEST_NAME), `${JSON.stringify(manifest, null, 2)}
20629
+ writeFileSync4(join7(targetDir, MANIFEST_NAME), `${JSON.stringify(manifest, null, 2)}
20506
20630
  `);
20507
20631
  }
20508
20632
  function readBundledVersion(sourceDir) {
20509
20633
  try {
20510
- return readFileSync5(join8(sourceDir, "VERSION"), "utf8").trim() || "unknown";
20634
+ return readFileSync5(join7(sourceDir, "VERSION"), "utf8").trim() || "unknown";
20511
20635
  } catch {
20512
20636
  return "unknown";
20513
20637
  }
20514
20638
  }
20515
20639
  function skillNames(sourceDir) {
20516
20640
  if (!existsSync4(sourceDir)) return [];
20517
- return readdirSync2(sourceDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => existsSync4(join8(sourceDir, name, "SKILL.md"))).sort();
20641
+ return readdirSync2(sourceDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => existsSync4(join7(sourceDir, name, "SKILL.md"))).sort();
20518
20642
  }
20519
20643
  function installBundledSkills(input2) {
20520
20644
  const result = { installed: [], updated: [], skipped: [], unchanged: [] };
@@ -20524,8 +20648,8 @@ function installBundledSkills(input2) {
20524
20648
  const manifest = readManifest(input2.targetDir) ?? { version: version2, skills: {} };
20525
20649
  const now = new Date(input2.now ?? Date.now()).toISOString();
20526
20650
  for (const name of skillNames(input2.sourceDir)) {
20527
- const source = join8(input2.sourceDir, name);
20528
- const target = join8(input2.targetDir, name);
20651
+ const source = join7(input2.sourceDir, name);
20652
+ const target = join7(input2.targetDir, name);
20529
20653
  const sourceHash = hashSkillDir(source);
20530
20654
  if (!existsSync4(target)) {
20531
20655
  cpSync(source, target, { recursive: true });
@@ -20552,7 +20676,7 @@ function installBundledSkills(input2) {
20552
20676
  result.unchanged.push(name);
20553
20677
  continue;
20554
20678
  }
20555
- rmSync2(target, { recursive: true, force: true });
20679
+ rmSync3(target, { recursive: true, force: true });
20556
20680
  cpSync(source, target, { recursive: true });
20557
20681
  manifest.skills[name] = { hash: sourceHash, updatedAt: now };
20558
20682
  result.updated.push(name);