appostle-installer 0.0.22 → 0.0.24
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/appostle.js +381 -367
- package/dist/appostle.js.map +4 -4
- package/dist/worker.js +834 -703
- package/dist/worker.js.map +4 -4
- package/package.json +1 -1
package/dist/appostle.js
CHANGED
|
@@ -3528,10 +3528,14 @@ var MutableDaemonConfigSchema = z11.object({
|
|
|
3528
3528
|
icon: z11.string().optional()
|
|
3529
3529
|
}).passthrough().optional(),
|
|
3530
3530
|
chrome: z11.object({
|
|
3531
|
-
// When false, agents on this host won't
|
|
3532
|
-
//
|
|
3533
|
-
//
|
|
3534
|
-
|
|
3531
|
+
// When false, agents on this host won't have the claude-in-chrome
|
|
3532
|
+
// tools injected. Set to false on hosts where the client's browser
|
|
3533
|
+
// should not be controlled (e.g. headless/server-only hosts).
|
|
3534
|
+
enabled: z11.boolean()
|
|
3535
|
+
}).passthrough().optional(),
|
|
3536
|
+
playwright: z11.object({
|
|
3537
|
+
// When false, agents on this host won't have the Playwright MCP
|
|
3538
|
+
// server injected.
|
|
3535
3539
|
enabled: z11.boolean()
|
|
3536
3540
|
}).passthrough().optional()
|
|
3537
3541
|
}).passthrough();
|
|
@@ -3540,7 +3544,8 @@ var MutableDaemonConfigPatchSchema = z11.object({
|
|
|
3540
3544
|
identity: z11.object({
|
|
3541
3545
|
icon: z11.string().optional()
|
|
3542
3546
|
}).partial().passthrough().optional(),
|
|
3543
|
-
chrome: MutableDaemonConfigSchema.shape.chrome
|
|
3547
|
+
chrome: MutableDaemonConfigSchema.shape.chrome,
|
|
3548
|
+
playwright: MutableDaemonConfigSchema.shape.playwright
|
|
3544
3549
|
}).partial().passthrough();
|
|
3545
3550
|
var AgentStatusSchema = z11.enum(AGENT_LIFECYCLE_STATUSES);
|
|
3546
3551
|
var AgentModeSchema = z11.object({
|
|
@@ -7694,6 +7699,9 @@ var PersistedConfigSchema = z14.object({
|
|
|
7694
7699
|
}).strict().optional(),
|
|
7695
7700
|
chrome: z14.object({
|
|
7696
7701
|
enabled: z14.boolean().optional()
|
|
7702
|
+
}).strict().optional(),
|
|
7703
|
+
playwright: z14.object({
|
|
7704
|
+
enabled: z14.boolean().optional()
|
|
7697
7705
|
}).strict().optional()
|
|
7698
7706
|
}).strict().transform(({ allowedHosts, ...daemon }) => {
|
|
7699
7707
|
const hostnames = daemon.hostnames ?? allowedHosts;
|
|
@@ -18514,6 +18522,7 @@ var ClaudeAgentClient = class {
|
|
|
18514
18522
|
runtimeSettings: this.runtimeSettings,
|
|
18515
18523
|
launchEnv: launchContext?.env,
|
|
18516
18524
|
chromeEnabled: launchContext?.chromeEnabled,
|
|
18525
|
+
playwrightEnabled: launchContext?.playwrightEnabled,
|
|
18517
18526
|
logger: this.logger,
|
|
18518
18527
|
queryFactory: this.queryFactory
|
|
18519
18528
|
});
|
|
@@ -18532,6 +18541,7 @@ var ClaudeAgentClient = class {
|
|
|
18532
18541
|
handle,
|
|
18533
18542
|
launchEnv: launchContext?.env,
|
|
18534
18543
|
chromeEnabled: launchContext?.chromeEnabled,
|
|
18544
|
+
playwrightEnabled: launchContext?.playwrightEnabled,
|
|
18535
18545
|
logger: this.logger,
|
|
18536
18546
|
queryFactory: this.queryFactory
|
|
18537
18547
|
});
|
|
@@ -18846,6 +18856,7 @@ var ClaudeAgentSession = class {
|
|
|
18846
18856
|
this.config = config;
|
|
18847
18857
|
this.launchEnv = options.launchEnv;
|
|
18848
18858
|
this.chromeEnabled = options.chromeEnabled ?? true;
|
|
18859
|
+
this.playwrightEnabled = options.playwrightEnabled ?? true;
|
|
18849
18860
|
this.defaults = options.defaults;
|
|
18850
18861
|
this.runtimeSettings = options.runtimeSettings;
|
|
18851
18862
|
this.logger = options.logger;
|
|
@@ -19609,7 +19620,8 @@ var ClaudeAgentSession = class {
|
|
|
19609
19620
|
} else {
|
|
19610
19621
|
const homeMcps = loadHomeMcpServers(this.logger);
|
|
19611
19622
|
if (homeMcps) {
|
|
19612
|
-
|
|
19623
|
+
const filtered = this.playwrightEnabled ? homeMcps : Object.fromEntries(Object.entries(homeMcps).filter(([k]) => k !== "playwright"));
|
|
19624
|
+
base.mcpServers = this.normalizeMcpServers(filtered);
|
|
19613
19625
|
}
|
|
19614
19626
|
}
|
|
19615
19627
|
if (this.config.model) {
|
|
@@ -31527,6 +31539,330 @@ function removeClaudeProfile(username, logger) {
|
|
|
31527
31539
|
import { z as z34 } from "zod";
|
|
31528
31540
|
import { getSessionMessages } from "@anthropic-ai/claude-agent-sdk";
|
|
31529
31541
|
|
|
31542
|
+
// ../server/src/server/skills/scanner.ts
|
|
31543
|
+
import fs8 from "node:fs/promises";
|
|
31544
|
+
import os6 from "node:os";
|
|
31545
|
+
import path14 from "node:path";
|
|
31546
|
+
var NAME_REGEX = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
31547
|
+
function homeDir() {
|
|
31548
|
+
return process.env.HOME || os6.homedir();
|
|
31549
|
+
}
|
|
31550
|
+
function codexHomeDir() {
|
|
31551
|
+
return process.env.CODEX_HOME || path14.join(homeDir(), ".codex");
|
|
31552
|
+
}
|
|
31553
|
+
function resolveScopeDir(provider, scope, workspaceRoot) {
|
|
31554
|
+
if (scope === "codex-prompts") {
|
|
31555
|
+
if (provider !== "codex") {
|
|
31556
|
+
throw new Error(`Scope "codex-prompts" is only valid for provider "codex"`);
|
|
31557
|
+
}
|
|
31558
|
+
return path14.join(codexHomeDir(), "prompts");
|
|
31559
|
+
}
|
|
31560
|
+
if (scope === "project") {
|
|
31561
|
+
if (!workspaceRoot) {
|
|
31562
|
+
throw new Error(`workspaceRoot is required for scope "project"`);
|
|
31563
|
+
}
|
|
31564
|
+
const dotDir = provider === "claude" ? ".claude" : ".codex";
|
|
31565
|
+
return path14.join(workspaceRoot, dotDir, "skills");
|
|
31566
|
+
}
|
|
31567
|
+
if (provider === "claude") {
|
|
31568
|
+
return path14.join(homeDir(), ".claude", "skills");
|
|
31569
|
+
}
|
|
31570
|
+
return path14.join(codexHomeDir(), "skills");
|
|
31571
|
+
}
|
|
31572
|
+
function allowedRoots(workspaceRoot) {
|
|
31573
|
+
const roots = [
|
|
31574
|
+
path14.join(homeDir(), ".claude", "skills"),
|
|
31575
|
+
path14.join(codexHomeDir(), "skills"),
|
|
31576
|
+
path14.join(codexHomeDir(), "prompts")
|
|
31577
|
+
];
|
|
31578
|
+
if (workspaceRoot) {
|
|
31579
|
+
roots.push(path14.join(workspaceRoot, ".claude", "skills"));
|
|
31580
|
+
roots.push(path14.join(workspaceRoot, ".codex", "skills"));
|
|
31581
|
+
}
|
|
31582
|
+
return roots.map((r) => path14.resolve(r));
|
|
31583
|
+
}
|
|
31584
|
+
function isInsideAllowedRoot(absPath, workspaceRoot) {
|
|
31585
|
+
const resolved = path14.resolve(absPath);
|
|
31586
|
+
for (const root of allowedRoots(workspaceRoot)) {
|
|
31587
|
+
const rel = path14.relative(root, resolved);
|
|
31588
|
+
if (rel === "" || !rel.startsWith("..") && !path14.isAbsolute(rel)) {
|
|
31589
|
+
return true;
|
|
31590
|
+
}
|
|
31591
|
+
}
|
|
31592
|
+
return false;
|
|
31593
|
+
}
|
|
31594
|
+
var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
31595
|
+
function parseSkillFile(text) {
|
|
31596
|
+
const match = FRONTMATTER_RE.exec(text);
|
|
31597
|
+
if (!match) {
|
|
31598
|
+
return { rawFrontmatterLines: [], body: text, hadFrontmatter: false };
|
|
31599
|
+
}
|
|
31600
|
+
const yamlBlock = match[1] ?? "";
|
|
31601
|
+
const body = match[2] ?? "";
|
|
31602
|
+
return {
|
|
31603
|
+
rawFrontmatterLines: yamlBlock.split(/\r?\n/),
|
|
31604
|
+
body,
|
|
31605
|
+
hadFrontmatter: true
|
|
31606
|
+
};
|
|
31607
|
+
}
|
|
31608
|
+
function readDescription(text) {
|
|
31609
|
+
const parsed = parseSkillFile(text);
|
|
31610
|
+
if (!parsed.hadFrontmatter) return "";
|
|
31611
|
+
for (const line of parsed.rawFrontmatterLines) {
|
|
31612
|
+
const m = /^description\s*:\s*(.*)$/.exec(line);
|
|
31613
|
+
if (m) {
|
|
31614
|
+
const value = (m[1] ?? "").trim();
|
|
31615
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
31616
|
+
return value.slice(1, -1);
|
|
31617
|
+
}
|
|
31618
|
+
return value;
|
|
31619
|
+
}
|
|
31620
|
+
}
|
|
31621
|
+
return "";
|
|
31622
|
+
}
|
|
31623
|
+
function findOwnedSpans(lines) {
|
|
31624
|
+
const spans = [];
|
|
31625
|
+
let i = 0;
|
|
31626
|
+
while (i < lines.length) {
|
|
31627
|
+
const line = lines[i] ?? "";
|
|
31628
|
+
const keyMatch = /^([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*)$/.exec(line);
|
|
31629
|
+
if (!keyMatch) {
|
|
31630
|
+
i++;
|
|
31631
|
+
continue;
|
|
31632
|
+
}
|
|
31633
|
+
const key = keyMatch[1];
|
|
31634
|
+
const value = keyMatch[2];
|
|
31635
|
+
if (key !== "description" && key !== "argument-hint" && key !== "allowed-tools") {
|
|
31636
|
+
i++;
|
|
31637
|
+
continue;
|
|
31638
|
+
}
|
|
31639
|
+
let end = i + 1;
|
|
31640
|
+
if (value === "") {
|
|
31641
|
+
while (end < lines.length) {
|
|
31642
|
+
const next = lines[end] ?? "";
|
|
31643
|
+
if (/^\s+-\s+/.test(next) || /^\s*$/.test(next)) {
|
|
31644
|
+
end++;
|
|
31645
|
+
} else {
|
|
31646
|
+
break;
|
|
31647
|
+
}
|
|
31648
|
+
}
|
|
31649
|
+
}
|
|
31650
|
+
spans.push({ key, startLine: i, endLine: end });
|
|
31651
|
+
i = end;
|
|
31652
|
+
}
|
|
31653
|
+
return spans;
|
|
31654
|
+
}
|
|
31655
|
+
function emitFrontmatterValue(key, value) {
|
|
31656
|
+
if (key === "allowed-tools") {
|
|
31657
|
+
if (!Array.isArray(value) || value.length === 0) return [];
|
|
31658
|
+
return [`allowed-tools:`, ...value.map((v) => ` - ${v}`)];
|
|
31659
|
+
}
|
|
31660
|
+
const text = typeof value === "string" ? value : "";
|
|
31661
|
+
if (text.length === 0) return [];
|
|
31662
|
+
const needsQuote = /[:#\n]/.test(text) || text.startsWith(" ") || text.endsWith(" ");
|
|
31663
|
+
const formatted = needsQuote ? `"${text.replace(/"/g, '\\"')}"` : text;
|
|
31664
|
+
return [`${key}: ${formatted}`];
|
|
31665
|
+
}
|
|
31666
|
+
function rewriteFrontmatter(originalLines, next) {
|
|
31667
|
+
const spans = findOwnedSpans(originalLines);
|
|
31668
|
+
const ownedKeys = new Set(spans.map((s) => s.key));
|
|
31669
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
31670
|
+
if (next.description !== void 0) {
|
|
31671
|
+
replacements.set("description", emitFrontmatterValue("description", next.description));
|
|
31672
|
+
}
|
|
31673
|
+
if (next.argumentHint !== void 0) {
|
|
31674
|
+
replacements.set("argument-hint", emitFrontmatterValue("argument-hint", next.argumentHint));
|
|
31675
|
+
}
|
|
31676
|
+
if (next.allowedTools !== void 0) {
|
|
31677
|
+
replacements.set("allowed-tools", emitFrontmatterValue("allowed-tools", next.allowedTools));
|
|
31678
|
+
}
|
|
31679
|
+
const out = [];
|
|
31680
|
+
let i = 0;
|
|
31681
|
+
while (i < originalLines.length) {
|
|
31682
|
+
const span = spans.find((s) => s.startLine === i);
|
|
31683
|
+
if (span) {
|
|
31684
|
+
if (replacements.has(span.key)) {
|
|
31685
|
+
out.push(...replacements.get(span.key) ?? []);
|
|
31686
|
+
} else {
|
|
31687
|
+
for (let j = span.startLine; j < span.endLine; j++) {
|
|
31688
|
+
out.push(originalLines[j] ?? "");
|
|
31689
|
+
}
|
|
31690
|
+
}
|
|
31691
|
+
i = span.endLine;
|
|
31692
|
+
continue;
|
|
31693
|
+
}
|
|
31694
|
+
out.push(originalLines[i] ?? "");
|
|
31695
|
+
i++;
|
|
31696
|
+
}
|
|
31697
|
+
for (const key of ["description", "argument-hint", "allowed-tools"]) {
|
|
31698
|
+
if (replacements.has(key) && !ownedKeys.has(key)) {
|
|
31699
|
+
out.push(...replacements.get(key) ?? []);
|
|
31700
|
+
}
|
|
31701
|
+
}
|
|
31702
|
+
return out;
|
|
31703
|
+
}
|
|
31704
|
+
async function listSkills(args) {
|
|
31705
|
+
const dir = resolveScopeDir(args.provider, args.scope, args.workspaceRoot);
|
|
31706
|
+
let entries;
|
|
31707
|
+
try {
|
|
31708
|
+
entries = await fs8.readdir(dir, { withFileTypes: true });
|
|
31709
|
+
} catch {
|
|
31710
|
+
return [];
|
|
31711
|
+
}
|
|
31712
|
+
const results = [];
|
|
31713
|
+
if (args.scope === "codex-prompts") {
|
|
31714
|
+
for (const entry of entries) {
|
|
31715
|
+
if (!entry.isFile()) continue;
|
|
31716
|
+
if (!entry.name.endsWith(".md")) continue;
|
|
31717
|
+
const name = entry.name.slice(0, -".md".length);
|
|
31718
|
+
if (!name) continue;
|
|
31719
|
+
const fullPath = path14.join(dir, entry.name);
|
|
31720
|
+
const stat5 = await safeStat(fullPath);
|
|
31721
|
+
if (!stat5) continue;
|
|
31722
|
+
const description = await readDescriptionSafely(fullPath);
|
|
31723
|
+
results.push({
|
|
31724
|
+
id: `${args.provider}:${args.scope}:${name}`,
|
|
31725
|
+
provider: args.provider,
|
|
31726
|
+
scope: args.scope,
|
|
31727
|
+
name,
|
|
31728
|
+
path: fullPath,
|
|
31729
|
+
description,
|
|
31730
|
+
modifiedAt: stat5.mtime.toISOString(),
|
|
31731
|
+
size: stat5.size
|
|
31732
|
+
});
|
|
31733
|
+
}
|
|
31734
|
+
} else {
|
|
31735
|
+
for (const entry of entries) {
|
|
31736
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
31737
|
+
const skillDir = path14.join(dir, entry.name);
|
|
31738
|
+
const skillPath = path14.join(skillDir, "SKILL.md");
|
|
31739
|
+
const stat5 = await safeStat(skillPath);
|
|
31740
|
+
if (!stat5) continue;
|
|
31741
|
+
const description = await readDescriptionSafely(skillPath);
|
|
31742
|
+
results.push({
|
|
31743
|
+
id: `${args.provider}:${args.scope}:${entry.name}`,
|
|
31744
|
+
provider: args.provider,
|
|
31745
|
+
scope: args.scope,
|
|
31746
|
+
name: entry.name,
|
|
31747
|
+
path: skillPath,
|
|
31748
|
+
description,
|
|
31749
|
+
modifiedAt: stat5.mtime.toISOString(),
|
|
31750
|
+
size: stat5.size
|
|
31751
|
+
});
|
|
31752
|
+
}
|
|
31753
|
+
}
|
|
31754
|
+
results.sort((a, b) => a.name.localeCompare(b.name));
|
|
31755
|
+
return results;
|
|
31756
|
+
}
|
|
31757
|
+
async function safeStat(filePath) {
|
|
31758
|
+
try {
|
|
31759
|
+
const s = await fs8.stat(filePath);
|
|
31760
|
+
return { mtime: s.mtime, size: s.size };
|
|
31761
|
+
} catch {
|
|
31762
|
+
return null;
|
|
31763
|
+
}
|
|
31764
|
+
}
|
|
31765
|
+
async function readDescriptionSafely(filePath) {
|
|
31766
|
+
try {
|
|
31767
|
+
const text = await fs8.readFile(filePath, "utf8");
|
|
31768
|
+
return readDescription(text);
|
|
31769
|
+
} catch {
|
|
31770
|
+
return "";
|
|
31771
|
+
}
|
|
31772
|
+
}
|
|
31773
|
+
async function createSkill(args) {
|
|
31774
|
+
if (!NAME_REGEX.test(args.name)) {
|
|
31775
|
+
throw new Error(
|
|
31776
|
+
`Invalid skill name "${args.name}". Use letters, digits, dot, underscore, dash.`
|
|
31777
|
+
);
|
|
31778
|
+
}
|
|
31779
|
+
if (args.name.includes("/") || args.name.includes("\\") || args.name.includes("..")) {
|
|
31780
|
+
throw new Error(`Skill name must not contain path separators or "..".`);
|
|
31781
|
+
}
|
|
31782
|
+
const dir = resolveScopeDir(args.provider, args.scope, args.workspaceRoot);
|
|
31783
|
+
await fs8.mkdir(dir, { recursive: true });
|
|
31784
|
+
if (args.scope === "codex-prompts") {
|
|
31785
|
+
const filePath2 = path14.join(dir, `${args.name}.md`);
|
|
31786
|
+
try {
|
|
31787
|
+
await fs8.access(filePath2);
|
|
31788
|
+
throw new Error(`A prompt named "${args.name}" already exists at ${filePath2}`);
|
|
31789
|
+
} catch (err) {
|
|
31790
|
+
if (err && typeof err === "object" && "code" in err && err.code !== "ENOENT") {
|
|
31791
|
+
throw err;
|
|
31792
|
+
}
|
|
31793
|
+
if (err instanceof Error && err.message.includes("already exists")) {
|
|
31794
|
+
throw err;
|
|
31795
|
+
}
|
|
31796
|
+
}
|
|
31797
|
+
const initial2 = buildStarterPrompt(args.name);
|
|
31798
|
+
await fs8.writeFile(filePath2, initial2, "utf8");
|
|
31799
|
+
return { path: filePath2 };
|
|
31800
|
+
}
|
|
31801
|
+
const skillDir = path14.join(dir, args.name);
|
|
31802
|
+
let dirExists = false;
|
|
31803
|
+
try {
|
|
31804
|
+
const stat5 = await fs8.stat(skillDir);
|
|
31805
|
+
dirExists = stat5.isDirectory();
|
|
31806
|
+
} catch {
|
|
31807
|
+
dirExists = false;
|
|
31808
|
+
}
|
|
31809
|
+
if (dirExists) {
|
|
31810
|
+
throw new Error(`A skill named "${args.name}" already exists at ${skillDir}`);
|
|
31811
|
+
}
|
|
31812
|
+
await fs8.mkdir(skillDir, { recursive: true });
|
|
31813
|
+
const filePath = path14.join(skillDir, "SKILL.md");
|
|
31814
|
+
const initial = buildStarterSkill(args.name);
|
|
31815
|
+
await fs8.writeFile(filePath, initial, "utf8");
|
|
31816
|
+
return { path: filePath };
|
|
31817
|
+
}
|
|
31818
|
+
function buildStarterSkill(name) {
|
|
31819
|
+
return `---
|
|
31820
|
+
name: ${name}
|
|
31821
|
+
description: ""
|
|
31822
|
+
---
|
|
31823
|
+
|
|
31824
|
+
# ${name}
|
|
31825
|
+
|
|
31826
|
+
Describe when this skill should be used and what it does. The body of this
|
|
31827
|
+
file is loaded into the agent's context when the skill is invoked.
|
|
31828
|
+
`;
|
|
31829
|
+
}
|
|
31830
|
+
function buildStarterPrompt(name) {
|
|
31831
|
+
return `---
|
|
31832
|
+
description: ""
|
|
31833
|
+
argument-hint: ""
|
|
31834
|
+
---
|
|
31835
|
+
|
|
31836
|
+
# ${name}
|
|
31837
|
+
|
|
31838
|
+
Body of the prompt. Use \`$1\`, \`$2\`, ... or \`$ARGUMENTS\` for parameter expansion.
|
|
31839
|
+
`;
|
|
31840
|
+
}
|
|
31841
|
+
async function writeSkillFrontmatter(args, workspaceRoot) {
|
|
31842
|
+
if (!path14.isAbsolute(args.path)) {
|
|
31843
|
+
throw new Error(`writeSkillFrontmatter expects an absolute path; got "${args.path}"`);
|
|
31844
|
+
}
|
|
31845
|
+
if (!isInsideAllowedRoot(args.path, workspaceRoot)) {
|
|
31846
|
+
throw new Error(`Path "${args.path}" is not inside an allowlisted skill root`);
|
|
31847
|
+
}
|
|
31848
|
+
let original;
|
|
31849
|
+
try {
|
|
31850
|
+
original = await fs8.readFile(args.path, "utf8");
|
|
31851
|
+
} catch (err) {
|
|
31852
|
+
throw new Error(
|
|
31853
|
+
`Failed to read skill file: ${err instanceof Error ? err.message : String(err)}`
|
|
31854
|
+
);
|
|
31855
|
+
}
|
|
31856
|
+
const parsed = parseSkillFile(original);
|
|
31857
|
+
const newLines = rewriteFrontmatter(parsed.rawFrontmatterLines, args.frontmatter);
|
|
31858
|
+
const nextFrontmatter = ["---", ...newLines, "---"].join("\n");
|
|
31859
|
+
const nextContent = parsed.hadFrontmatter ? `${nextFrontmatter}
|
|
31860
|
+
${parsed.body}` : `${nextFrontmatter}
|
|
31861
|
+
|
|
31862
|
+
${original}`;
|
|
31863
|
+
await fs8.writeFile(args.path, nextContent, "utf8");
|
|
31864
|
+
}
|
|
31865
|
+
|
|
31530
31866
|
// ../server/src/server/agent/handoff-mcp.ts
|
|
31531
31867
|
import { createSdkMcpServer, tool as tool2 } from "@anthropic-ai/claude-agent-sdk";
|
|
31532
31868
|
import { z as z33 } from "zod";
|
|
@@ -32499,8 +32835,8 @@ function isVoicePermissionAllowed(request) {
|
|
|
32499
32835
|
}
|
|
32500
32836
|
|
|
32501
32837
|
// ../server/src/server/file-explorer/service.ts
|
|
32502
|
-
import { promises as
|
|
32503
|
-
import
|
|
32838
|
+
import { promises as fs9 } from "fs";
|
|
32839
|
+
import path15 from "path";
|
|
32504
32840
|
|
|
32505
32841
|
// ../server/src/server/path-utils.ts
|
|
32506
32842
|
import { homedir as homedir2 } from "node:os";
|
|
@@ -32547,14 +32883,14 @@ async function listDirectoryEntries({
|
|
|
32547
32883
|
relativePath = "."
|
|
32548
32884
|
}) {
|
|
32549
32885
|
const directoryPath = await resolveScopedPath({ root, relativePath });
|
|
32550
|
-
const stats = await
|
|
32886
|
+
const stats = await fs9.stat(directoryPath);
|
|
32551
32887
|
if (!stats.isDirectory()) {
|
|
32552
32888
|
throw new Error("Requested path is not a directory");
|
|
32553
32889
|
}
|
|
32554
|
-
const dirents = await
|
|
32890
|
+
const dirents = await fs9.readdir(directoryPath, { withFileTypes: true });
|
|
32555
32891
|
const entriesWithNulls = await Promise.all(
|
|
32556
32892
|
dirents.map(async (dirent) => {
|
|
32557
|
-
const targetPath =
|
|
32893
|
+
const targetPath = path15.join(directoryPath, dirent.name);
|
|
32558
32894
|
const kind = dirent.isDirectory() ? "directory" : "file";
|
|
32559
32895
|
try {
|
|
32560
32896
|
return await buildEntryPayload({
|
|
@@ -32589,18 +32925,18 @@ async function readExplorerFile({
|
|
|
32589
32925
|
relativePath
|
|
32590
32926
|
}) {
|
|
32591
32927
|
const filePath = await resolveScopedPath({ root, relativePath });
|
|
32592
|
-
const stats = await
|
|
32928
|
+
const stats = await fs9.stat(filePath);
|
|
32593
32929
|
if (!stats.isFile()) {
|
|
32594
32930
|
throw new Error("Requested path is not a file");
|
|
32595
32931
|
}
|
|
32596
|
-
const ext =
|
|
32932
|
+
const ext = path15.extname(filePath).toLowerCase();
|
|
32597
32933
|
const basePayload = {
|
|
32598
32934
|
path: normalizeRelativePath({ root, targetPath: filePath }),
|
|
32599
32935
|
size: stats.size,
|
|
32600
32936
|
modifiedAt: stats.mtime.toISOString()
|
|
32601
32937
|
};
|
|
32602
32938
|
if (ext in IMAGE_MIME_TYPES) {
|
|
32603
|
-
const buffer2 = await
|
|
32939
|
+
const buffer2 = await fs9.readFile(filePath);
|
|
32604
32940
|
return {
|
|
32605
32941
|
...basePayload,
|
|
32606
32942
|
kind: "image",
|
|
@@ -32609,7 +32945,7 @@ async function readExplorerFile({
|
|
|
32609
32945
|
mimeType: IMAGE_MIME_TYPES[ext]
|
|
32610
32946
|
};
|
|
32611
32947
|
}
|
|
32612
|
-
const buffer = await
|
|
32948
|
+
const buffer = await fs9.readFile(filePath);
|
|
32613
32949
|
if (isLikelyBinary(buffer)) {
|
|
32614
32950
|
return {
|
|
32615
32951
|
...basePayload,
|
|
@@ -32631,34 +32967,34 @@ async function writeTextFile({
|
|
|
32631
32967
|
relativePath,
|
|
32632
32968
|
content
|
|
32633
32969
|
}) {
|
|
32634
|
-
const ext =
|
|
32970
|
+
const ext = path15.extname(relativePath).toLowerCase();
|
|
32635
32971
|
if (ext in IMAGE_MIME_TYPES) {
|
|
32636
32972
|
throw new Error(`Refusing to write '${relativePath}': binary/image file`);
|
|
32637
32973
|
}
|
|
32638
32974
|
const filePath = await resolveScopedPath({ root, relativePath });
|
|
32639
32975
|
const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
32640
|
-
await
|
|
32641
|
-
await
|
|
32976
|
+
await fs9.writeFile(tempPath, content, "utf8");
|
|
32977
|
+
await fs9.rename(tempPath, filePath);
|
|
32642
32978
|
}
|
|
32643
32979
|
async function deleteFile({ root, relativePath }) {
|
|
32644
|
-
const ext =
|
|
32980
|
+
const ext = path15.extname(relativePath).toLowerCase();
|
|
32645
32981
|
if (ext !== ".md") {
|
|
32646
32982
|
throw new Error(`Refusing to delete '${relativePath}': only .md files allowed`);
|
|
32647
32983
|
}
|
|
32648
32984
|
const filePath = await resolveScopedPath({ root, relativePath });
|
|
32649
|
-
const stats = await
|
|
32985
|
+
const stats = await fs9.stat(filePath);
|
|
32650
32986
|
if (!stats.isFile()) {
|
|
32651
32987
|
throw new Error("Requested path is not a file");
|
|
32652
32988
|
}
|
|
32653
|
-
await
|
|
32989
|
+
await fs9.unlink(filePath);
|
|
32654
32990
|
}
|
|
32655
32991
|
async function deleteEntry({ root, relativePath }) {
|
|
32656
32992
|
const entryPath = await resolveScopedPath({ root, relativePath });
|
|
32657
|
-
await
|
|
32993
|
+
await fs9.rm(entryPath, { recursive: true, force: false });
|
|
32658
32994
|
}
|
|
32659
32995
|
async function createDirectory({ root, relativePath }) {
|
|
32660
32996
|
const dirPath = await resolveScopedPath({ root, relativePath });
|
|
32661
|
-
await
|
|
32997
|
+
await fs9.mkdir(dirPath, { recursive: true });
|
|
32662
32998
|
}
|
|
32663
32999
|
async function moveEntry({
|
|
32664
33000
|
root,
|
|
@@ -32667,22 +33003,22 @@ async function moveEntry({
|
|
|
32667
33003
|
}) {
|
|
32668
33004
|
const src = await resolveScopedPath({ root, relativePath: sourcePath });
|
|
32669
33005
|
const dest = await resolveScopedPath({ root, relativePath: destinationPath });
|
|
32670
|
-
await
|
|
33006
|
+
await fs9.rename(src, dest);
|
|
32671
33007
|
}
|
|
32672
33008
|
async function getDownloadableFileInfo({ root, relativePath }) {
|
|
32673
33009
|
const filePath = await resolveScopedPath({ root, relativePath });
|
|
32674
|
-
const stats = await
|
|
33010
|
+
const stats = await fs9.stat(filePath);
|
|
32675
33011
|
if (!stats.isFile()) {
|
|
32676
33012
|
throw new Error("Requested path is not a file");
|
|
32677
33013
|
}
|
|
32678
|
-
const ext =
|
|
33014
|
+
const ext = path15.extname(filePath).toLowerCase();
|
|
32679
33015
|
let mimeType = "application/octet-stream";
|
|
32680
33016
|
if (ext in IMAGE_MIME_TYPES) {
|
|
32681
33017
|
mimeType = IMAGE_MIME_TYPES[ext] ?? mimeType;
|
|
32682
33018
|
} else if (ext in FONT_MIME_TYPES) {
|
|
32683
33019
|
mimeType = FONT_MIME_TYPES[ext] ?? mimeType;
|
|
32684
33020
|
} else {
|
|
32685
|
-
const handle = await
|
|
33021
|
+
const handle = await fs9.open(filePath, "r");
|
|
32686
33022
|
const sample = Buffer.alloc(8192);
|
|
32687
33023
|
try {
|
|
32688
33024
|
const { bytesRead } = await handle.read(sample, 0, sample.length, 0);
|
|
@@ -32697,23 +33033,23 @@ async function getDownloadableFileInfo({ root, relativePath }) {
|
|
|
32697
33033
|
return {
|
|
32698
33034
|
path: normalizeRelativePath({ root, targetPath: filePath }),
|
|
32699
33035
|
absolutePath: filePath,
|
|
32700
|
-
fileName:
|
|
33036
|
+
fileName: path15.basename(filePath),
|
|
32701
33037
|
mimeType,
|
|
32702
33038
|
size: stats.size
|
|
32703
33039
|
};
|
|
32704
33040
|
}
|
|
32705
33041
|
async function resolveScopedPath({ root, relativePath = "." }) {
|
|
32706
|
-
const normalizedRoot =
|
|
33042
|
+
const normalizedRoot = path15.resolve(root);
|
|
32707
33043
|
const requestedPath = resolvePathFromBase(normalizedRoot, relativePath);
|
|
32708
|
-
const relative =
|
|
32709
|
-
if (relative !== "" && (relative.startsWith("..") ||
|
|
33044
|
+
const relative = path15.relative(normalizedRoot, requestedPath);
|
|
33045
|
+
if (relative !== "" && (relative.startsWith("..") || path15.isAbsolute(relative))) {
|
|
32710
33046
|
throw new Error("Access outside of workspace is not allowed");
|
|
32711
33047
|
}
|
|
32712
|
-
const realRoot = await
|
|
33048
|
+
const realRoot = await fs9.realpath(normalizedRoot);
|
|
32713
33049
|
try {
|
|
32714
|
-
const realPath = await
|
|
32715
|
-
const realRelative =
|
|
32716
|
-
if (realRelative !== "" && (realRelative.startsWith("..") ||
|
|
33050
|
+
const realPath = await fs9.realpath(requestedPath);
|
|
33051
|
+
const realRelative = path15.relative(realRoot, realPath);
|
|
33052
|
+
if (realRelative !== "" && (realRelative.startsWith("..") || path15.isAbsolute(realRelative))) {
|
|
32717
33053
|
throw new Error("Access outside of workspace is not allowed");
|
|
32718
33054
|
}
|
|
32719
33055
|
return requestedPath;
|
|
@@ -32730,7 +33066,7 @@ async function buildEntryPayload({
|
|
|
32730
33066
|
name,
|
|
32731
33067
|
kind
|
|
32732
33068
|
}) {
|
|
32733
|
-
const stats = await
|
|
33069
|
+
const stats = await fs9.stat(targetPath);
|
|
32734
33070
|
return {
|
|
32735
33071
|
name,
|
|
32736
33072
|
path: normalizeRelativePath({ root, targetPath }),
|
|
@@ -32744,10 +33080,10 @@ function isMissingEntryError(error) {
|
|
|
32744
33080
|
return code === "ENOENT" || code === "ENOTDIR" || code === "ELOOP";
|
|
32745
33081
|
}
|
|
32746
33082
|
function normalizeRelativePath({ root, targetPath }) {
|
|
32747
|
-
const normalizedRoot =
|
|
32748
|
-
const normalizedTarget =
|
|
32749
|
-
const relative =
|
|
32750
|
-
return relative === "" ? "." : relative.split(
|
|
33083
|
+
const normalizedRoot = path15.resolve(root);
|
|
33084
|
+
const normalizedTarget = path15.resolve(targetPath);
|
|
33085
|
+
const relative = path15.relative(normalizedRoot, normalizedTarget);
|
|
33086
|
+
return relative === "" ? "." : relative.split(path15.sep).join("/");
|
|
32751
33087
|
}
|
|
32752
33088
|
function textMimeTypeForExtension(ext) {
|
|
32753
33089
|
return TEXT_MIME_TYPES[ext] ?? DEFAULT_TEXT_MIME_TYPE;
|
|
@@ -33100,342 +33436,18 @@ async function getProjectIcon(projectDir) {
|
|
|
33100
33436
|
}
|
|
33101
33437
|
|
|
33102
33438
|
// ../server/src/utils/path.ts
|
|
33103
|
-
import
|
|
33439
|
+
import os7 from "os";
|
|
33104
33440
|
function expandTilde(path29) {
|
|
33105
33441
|
if (path29.startsWith("~/")) {
|
|
33106
|
-
const homeDir3 = process.env.HOME ||
|
|
33442
|
+
const homeDir3 = process.env.HOME || os7.homedir();
|
|
33107
33443
|
return path29.replace("~", homeDir3);
|
|
33108
33444
|
}
|
|
33109
33445
|
if (path29 === "~") {
|
|
33110
|
-
return process.env.HOME ||
|
|
33446
|
+
return process.env.HOME || os7.homedir();
|
|
33111
33447
|
}
|
|
33112
33448
|
return path29;
|
|
33113
33449
|
}
|
|
33114
33450
|
|
|
33115
|
-
// ../server/src/server/skills/scanner.ts
|
|
33116
|
-
import fs9 from "node:fs/promises";
|
|
33117
|
-
import os7 from "node:os";
|
|
33118
|
-
import path15 from "node:path";
|
|
33119
|
-
var NAME_REGEX = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
33120
|
-
function homeDir() {
|
|
33121
|
-
return process.env.HOME || os7.homedir();
|
|
33122
|
-
}
|
|
33123
|
-
function codexHomeDir() {
|
|
33124
|
-
return process.env.CODEX_HOME || path15.join(homeDir(), ".codex");
|
|
33125
|
-
}
|
|
33126
|
-
function resolveScopeDir(provider, scope, workspaceRoot) {
|
|
33127
|
-
if (scope === "codex-prompts") {
|
|
33128
|
-
if (provider !== "codex") {
|
|
33129
|
-
throw new Error(`Scope "codex-prompts" is only valid for provider "codex"`);
|
|
33130
|
-
}
|
|
33131
|
-
return path15.join(codexHomeDir(), "prompts");
|
|
33132
|
-
}
|
|
33133
|
-
if (scope === "project") {
|
|
33134
|
-
if (!workspaceRoot) {
|
|
33135
|
-
throw new Error(`workspaceRoot is required for scope "project"`);
|
|
33136
|
-
}
|
|
33137
|
-
const dotDir = provider === "claude" ? ".claude" : ".codex";
|
|
33138
|
-
return path15.join(workspaceRoot, dotDir, "skills");
|
|
33139
|
-
}
|
|
33140
|
-
if (provider === "claude") {
|
|
33141
|
-
return path15.join(homeDir(), ".claude", "skills");
|
|
33142
|
-
}
|
|
33143
|
-
return path15.join(codexHomeDir(), "skills");
|
|
33144
|
-
}
|
|
33145
|
-
function allowedRoots(workspaceRoot) {
|
|
33146
|
-
const roots = [
|
|
33147
|
-
path15.join(homeDir(), ".claude", "skills"),
|
|
33148
|
-
path15.join(codexHomeDir(), "skills"),
|
|
33149
|
-
path15.join(codexHomeDir(), "prompts")
|
|
33150
|
-
];
|
|
33151
|
-
if (workspaceRoot) {
|
|
33152
|
-
roots.push(path15.join(workspaceRoot, ".claude", "skills"));
|
|
33153
|
-
roots.push(path15.join(workspaceRoot, ".codex", "skills"));
|
|
33154
|
-
}
|
|
33155
|
-
return roots.map((r) => path15.resolve(r));
|
|
33156
|
-
}
|
|
33157
|
-
function isInsideAllowedRoot(absPath, workspaceRoot) {
|
|
33158
|
-
const resolved = path15.resolve(absPath);
|
|
33159
|
-
for (const root of allowedRoots(workspaceRoot)) {
|
|
33160
|
-
const rel = path15.relative(root, resolved);
|
|
33161
|
-
if (rel === "" || !rel.startsWith("..") && !path15.isAbsolute(rel)) {
|
|
33162
|
-
return true;
|
|
33163
|
-
}
|
|
33164
|
-
}
|
|
33165
|
-
return false;
|
|
33166
|
-
}
|
|
33167
|
-
var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
33168
|
-
function parseSkillFile(text) {
|
|
33169
|
-
const match = FRONTMATTER_RE.exec(text);
|
|
33170
|
-
if (!match) {
|
|
33171
|
-
return { rawFrontmatterLines: [], body: text, hadFrontmatter: false };
|
|
33172
|
-
}
|
|
33173
|
-
const yamlBlock = match[1] ?? "";
|
|
33174
|
-
const body = match[2] ?? "";
|
|
33175
|
-
return {
|
|
33176
|
-
rawFrontmatterLines: yamlBlock.split(/\r?\n/),
|
|
33177
|
-
body,
|
|
33178
|
-
hadFrontmatter: true
|
|
33179
|
-
};
|
|
33180
|
-
}
|
|
33181
|
-
function readDescription(text) {
|
|
33182
|
-
const parsed = parseSkillFile(text);
|
|
33183
|
-
if (!parsed.hadFrontmatter) return "";
|
|
33184
|
-
for (const line of parsed.rawFrontmatterLines) {
|
|
33185
|
-
const m = /^description\s*:\s*(.*)$/.exec(line);
|
|
33186
|
-
if (m) {
|
|
33187
|
-
const value = (m[1] ?? "").trim();
|
|
33188
|
-
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
33189
|
-
return value.slice(1, -1);
|
|
33190
|
-
}
|
|
33191
|
-
return value;
|
|
33192
|
-
}
|
|
33193
|
-
}
|
|
33194
|
-
return "";
|
|
33195
|
-
}
|
|
33196
|
-
function findOwnedSpans(lines) {
|
|
33197
|
-
const spans = [];
|
|
33198
|
-
let i = 0;
|
|
33199
|
-
while (i < lines.length) {
|
|
33200
|
-
const line = lines[i] ?? "";
|
|
33201
|
-
const keyMatch = /^([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*)$/.exec(line);
|
|
33202
|
-
if (!keyMatch) {
|
|
33203
|
-
i++;
|
|
33204
|
-
continue;
|
|
33205
|
-
}
|
|
33206
|
-
const key = keyMatch[1];
|
|
33207
|
-
const value = keyMatch[2];
|
|
33208
|
-
if (key !== "description" && key !== "argument-hint" && key !== "allowed-tools") {
|
|
33209
|
-
i++;
|
|
33210
|
-
continue;
|
|
33211
|
-
}
|
|
33212
|
-
let end = i + 1;
|
|
33213
|
-
if (value === "") {
|
|
33214
|
-
while (end < lines.length) {
|
|
33215
|
-
const next = lines[end] ?? "";
|
|
33216
|
-
if (/^\s+-\s+/.test(next) || /^\s*$/.test(next)) {
|
|
33217
|
-
end++;
|
|
33218
|
-
} else {
|
|
33219
|
-
break;
|
|
33220
|
-
}
|
|
33221
|
-
}
|
|
33222
|
-
}
|
|
33223
|
-
spans.push({ key, startLine: i, endLine: end });
|
|
33224
|
-
i = end;
|
|
33225
|
-
}
|
|
33226
|
-
return spans;
|
|
33227
|
-
}
|
|
33228
|
-
function emitFrontmatterValue(key, value) {
|
|
33229
|
-
if (key === "allowed-tools") {
|
|
33230
|
-
if (!Array.isArray(value) || value.length === 0) return [];
|
|
33231
|
-
return [`allowed-tools:`, ...value.map((v) => ` - ${v}`)];
|
|
33232
|
-
}
|
|
33233
|
-
const text = typeof value === "string" ? value : "";
|
|
33234
|
-
if (text.length === 0) return [];
|
|
33235
|
-
const needsQuote = /[:#\n]/.test(text) || text.startsWith(" ") || text.endsWith(" ");
|
|
33236
|
-
const formatted = needsQuote ? `"${text.replace(/"/g, '\\"')}"` : text;
|
|
33237
|
-
return [`${key}: ${formatted}`];
|
|
33238
|
-
}
|
|
33239
|
-
function rewriteFrontmatter(originalLines, next) {
|
|
33240
|
-
const spans = findOwnedSpans(originalLines);
|
|
33241
|
-
const ownedKeys = new Set(spans.map((s) => s.key));
|
|
33242
|
-
const replacements = /* @__PURE__ */ new Map();
|
|
33243
|
-
if (next.description !== void 0) {
|
|
33244
|
-
replacements.set("description", emitFrontmatterValue("description", next.description));
|
|
33245
|
-
}
|
|
33246
|
-
if (next.argumentHint !== void 0) {
|
|
33247
|
-
replacements.set("argument-hint", emitFrontmatterValue("argument-hint", next.argumentHint));
|
|
33248
|
-
}
|
|
33249
|
-
if (next.allowedTools !== void 0) {
|
|
33250
|
-
replacements.set("allowed-tools", emitFrontmatterValue("allowed-tools", next.allowedTools));
|
|
33251
|
-
}
|
|
33252
|
-
const out = [];
|
|
33253
|
-
let i = 0;
|
|
33254
|
-
while (i < originalLines.length) {
|
|
33255
|
-
const span = spans.find((s) => s.startLine === i);
|
|
33256
|
-
if (span) {
|
|
33257
|
-
if (replacements.has(span.key)) {
|
|
33258
|
-
out.push(...replacements.get(span.key) ?? []);
|
|
33259
|
-
} else {
|
|
33260
|
-
for (let j = span.startLine; j < span.endLine; j++) {
|
|
33261
|
-
out.push(originalLines[j] ?? "");
|
|
33262
|
-
}
|
|
33263
|
-
}
|
|
33264
|
-
i = span.endLine;
|
|
33265
|
-
continue;
|
|
33266
|
-
}
|
|
33267
|
-
out.push(originalLines[i] ?? "");
|
|
33268
|
-
i++;
|
|
33269
|
-
}
|
|
33270
|
-
for (const key of ["description", "argument-hint", "allowed-tools"]) {
|
|
33271
|
-
if (replacements.has(key) && !ownedKeys.has(key)) {
|
|
33272
|
-
out.push(...replacements.get(key) ?? []);
|
|
33273
|
-
}
|
|
33274
|
-
}
|
|
33275
|
-
return out;
|
|
33276
|
-
}
|
|
33277
|
-
async function listSkills(args) {
|
|
33278
|
-
const dir = resolveScopeDir(args.provider, args.scope, args.workspaceRoot);
|
|
33279
|
-
let entries;
|
|
33280
|
-
try {
|
|
33281
|
-
entries = await fs9.readdir(dir, { withFileTypes: true });
|
|
33282
|
-
} catch {
|
|
33283
|
-
return [];
|
|
33284
|
-
}
|
|
33285
|
-
const results = [];
|
|
33286
|
-
if (args.scope === "codex-prompts") {
|
|
33287
|
-
for (const entry of entries) {
|
|
33288
|
-
if (!entry.isFile()) continue;
|
|
33289
|
-
if (!entry.name.endsWith(".md")) continue;
|
|
33290
|
-
const name = entry.name.slice(0, -".md".length);
|
|
33291
|
-
if (!name) continue;
|
|
33292
|
-
const fullPath = path15.join(dir, entry.name);
|
|
33293
|
-
const stat5 = await safeStat(fullPath);
|
|
33294
|
-
if (!stat5) continue;
|
|
33295
|
-
const description = await readDescriptionSafely(fullPath);
|
|
33296
|
-
results.push({
|
|
33297
|
-
id: `${args.provider}:${args.scope}:${name}`,
|
|
33298
|
-
provider: args.provider,
|
|
33299
|
-
scope: args.scope,
|
|
33300
|
-
name,
|
|
33301
|
-
path: fullPath,
|
|
33302
|
-
description,
|
|
33303
|
-
modifiedAt: stat5.mtime.toISOString(),
|
|
33304
|
-
size: stat5.size
|
|
33305
|
-
});
|
|
33306
|
-
}
|
|
33307
|
-
} else {
|
|
33308
|
-
for (const entry of entries) {
|
|
33309
|
-
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
33310
|
-
const skillDir = path15.join(dir, entry.name);
|
|
33311
|
-
const skillPath = path15.join(skillDir, "SKILL.md");
|
|
33312
|
-
const stat5 = await safeStat(skillPath);
|
|
33313
|
-
if (!stat5) continue;
|
|
33314
|
-
const description = await readDescriptionSafely(skillPath);
|
|
33315
|
-
results.push({
|
|
33316
|
-
id: `${args.provider}:${args.scope}:${entry.name}`,
|
|
33317
|
-
provider: args.provider,
|
|
33318
|
-
scope: args.scope,
|
|
33319
|
-
name: entry.name,
|
|
33320
|
-
path: skillPath,
|
|
33321
|
-
description,
|
|
33322
|
-
modifiedAt: stat5.mtime.toISOString(),
|
|
33323
|
-
size: stat5.size
|
|
33324
|
-
});
|
|
33325
|
-
}
|
|
33326
|
-
}
|
|
33327
|
-
results.sort((a, b) => a.name.localeCompare(b.name));
|
|
33328
|
-
return results;
|
|
33329
|
-
}
|
|
33330
|
-
async function safeStat(filePath) {
|
|
33331
|
-
try {
|
|
33332
|
-
const s = await fs9.stat(filePath);
|
|
33333
|
-
return { mtime: s.mtime, size: s.size };
|
|
33334
|
-
} catch {
|
|
33335
|
-
return null;
|
|
33336
|
-
}
|
|
33337
|
-
}
|
|
33338
|
-
async function readDescriptionSafely(filePath) {
|
|
33339
|
-
try {
|
|
33340
|
-
const text = await fs9.readFile(filePath, "utf8");
|
|
33341
|
-
return readDescription(text);
|
|
33342
|
-
} catch {
|
|
33343
|
-
return "";
|
|
33344
|
-
}
|
|
33345
|
-
}
|
|
33346
|
-
async function createSkill(args) {
|
|
33347
|
-
if (!NAME_REGEX.test(args.name)) {
|
|
33348
|
-
throw new Error(
|
|
33349
|
-
`Invalid skill name "${args.name}". Use letters, digits, dot, underscore, dash.`
|
|
33350
|
-
);
|
|
33351
|
-
}
|
|
33352
|
-
if (args.name.includes("/") || args.name.includes("\\") || args.name.includes("..")) {
|
|
33353
|
-
throw new Error(`Skill name must not contain path separators or "..".`);
|
|
33354
|
-
}
|
|
33355
|
-
const dir = resolveScopeDir(args.provider, args.scope, args.workspaceRoot);
|
|
33356
|
-
await fs9.mkdir(dir, { recursive: true });
|
|
33357
|
-
if (args.scope === "codex-prompts") {
|
|
33358
|
-
const filePath2 = path15.join(dir, `${args.name}.md`);
|
|
33359
|
-
try {
|
|
33360
|
-
await fs9.access(filePath2);
|
|
33361
|
-
throw new Error(`A prompt named "${args.name}" already exists at ${filePath2}`);
|
|
33362
|
-
} catch (err) {
|
|
33363
|
-
if (err && typeof err === "object" && "code" in err && err.code !== "ENOENT") {
|
|
33364
|
-
throw err;
|
|
33365
|
-
}
|
|
33366
|
-
if (err instanceof Error && err.message.includes("already exists")) {
|
|
33367
|
-
throw err;
|
|
33368
|
-
}
|
|
33369
|
-
}
|
|
33370
|
-
const initial2 = buildStarterPrompt(args.name);
|
|
33371
|
-
await fs9.writeFile(filePath2, initial2, "utf8");
|
|
33372
|
-
return { path: filePath2 };
|
|
33373
|
-
}
|
|
33374
|
-
const skillDir = path15.join(dir, args.name);
|
|
33375
|
-
let dirExists = false;
|
|
33376
|
-
try {
|
|
33377
|
-
const stat5 = await fs9.stat(skillDir);
|
|
33378
|
-
dirExists = stat5.isDirectory();
|
|
33379
|
-
} catch {
|
|
33380
|
-
dirExists = false;
|
|
33381
|
-
}
|
|
33382
|
-
if (dirExists) {
|
|
33383
|
-
throw new Error(`A skill named "${args.name}" already exists at ${skillDir}`);
|
|
33384
|
-
}
|
|
33385
|
-
await fs9.mkdir(skillDir, { recursive: true });
|
|
33386
|
-
const filePath = path15.join(skillDir, "SKILL.md");
|
|
33387
|
-
const initial = buildStarterSkill(args.name);
|
|
33388
|
-
await fs9.writeFile(filePath, initial, "utf8");
|
|
33389
|
-
return { path: filePath };
|
|
33390
|
-
}
|
|
33391
|
-
function buildStarterSkill(name) {
|
|
33392
|
-
return `---
|
|
33393
|
-
name: ${name}
|
|
33394
|
-
description: ""
|
|
33395
|
-
---
|
|
33396
|
-
|
|
33397
|
-
# ${name}
|
|
33398
|
-
|
|
33399
|
-
Describe when this skill should be used and what it does. The body of this
|
|
33400
|
-
file is loaded into the agent's context when the skill is invoked.
|
|
33401
|
-
`;
|
|
33402
|
-
}
|
|
33403
|
-
function buildStarterPrompt(name) {
|
|
33404
|
-
return `---
|
|
33405
|
-
description: ""
|
|
33406
|
-
argument-hint: ""
|
|
33407
|
-
---
|
|
33408
|
-
|
|
33409
|
-
# ${name}
|
|
33410
|
-
|
|
33411
|
-
Body of the prompt. Use \`$1\`, \`$2\`, ... or \`$ARGUMENTS\` for parameter expansion.
|
|
33412
|
-
`;
|
|
33413
|
-
}
|
|
33414
|
-
async function writeSkillFrontmatter(args, workspaceRoot) {
|
|
33415
|
-
if (!path15.isAbsolute(args.path)) {
|
|
33416
|
-
throw new Error(`writeSkillFrontmatter expects an absolute path; got "${args.path}"`);
|
|
33417
|
-
}
|
|
33418
|
-
if (!isInsideAllowedRoot(args.path, workspaceRoot)) {
|
|
33419
|
-
throw new Error(`Path "${args.path}" is not inside an allowlisted skill root`);
|
|
33420
|
-
}
|
|
33421
|
-
let original;
|
|
33422
|
-
try {
|
|
33423
|
-
original = await fs9.readFile(args.path, "utf8");
|
|
33424
|
-
} catch (err) {
|
|
33425
|
-
throw new Error(
|
|
33426
|
-
`Failed to read skill file: ${err instanceof Error ? err.message : String(err)}`
|
|
33427
|
-
);
|
|
33428
|
-
}
|
|
33429
|
-
const parsed = parseSkillFile(original);
|
|
33430
|
-
const newLines = rewriteFrontmatter(parsed.rawFrontmatterLines, args.frontmatter);
|
|
33431
|
-
const nextFrontmatter = ["---", ...newLines, "---"].join("\n");
|
|
33432
|
-
const nextContent = parsed.hadFrontmatter ? `${nextFrontmatter}
|
|
33433
|
-
${parsed.body}` : `${nextFrontmatter}
|
|
33434
|
-
|
|
33435
|
-
${original}`;
|
|
33436
|
-
await fs9.writeFile(args.path, nextContent, "utf8");
|
|
33437
|
-
}
|
|
33438
|
-
|
|
33439
33451
|
// ../server/src/utils/directory-suggestions.ts
|
|
33440
33452
|
import { readdir as readdir2, realpath, stat as stat3 } from "node:fs/promises";
|
|
33441
33453
|
import path16 from "node:path";
|
|
@@ -47275,6 +47287,7 @@ function loadConfig(appostleHome, options) {
|
|
|
47275
47287
|
const mcpEnabled = options?.cli?.mcpEnabled ?? persisted.daemon?.mcp?.enabled ?? true;
|
|
47276
47288
|
const mcpInjectIntoAgents = options?.cli?.mcpInjectIntoAgents ?? persisted.daemon?.mcp?.injectIntoAgents ?? false;
|
|
47277
47289
|
const chromeEnabled = persisted.daemon?.chrome?.enabled ?? true;
|
|
47290
|
+
const playwrightEnabled = persisted.daemon?.playwright?.enabled ?? true;
|
|
47278
47291
|
const daemonIcon = persisted.daemon?.identity?.icon;
|
|
47279
47292
|
const relayEnabled = options?.cli?.relayEnabled ?? parseBooleanEnv(env.APPOSTLE_RELAY_ENABLED) ?? persisted.daemon?.relay?.enabled ?? true;
|
|
47280
47293
|
const relayEndpoint = env.APPOSTLE_RELAY_ENDPOINT ?? persisted.daemon?.relay?.endpoint ?? DEFAULT_RELAY_ENDPOINT;
|
|
@@ -47305,6 +47318,7 @@ function loadConfig(appostleHome, options) {
|
|
|
47305
47318
|
mcpEnabled,
|
|
47306
47319
|
mcpInjectIntoAgents,
|
|
47307
47320
|
chromeEnabled,
|
|
47321
|
+
playwrightEnabled,
|
|
47308
47322
|
mcpDebug: env.MCP_DEBUG === "1",
|
|
47309
47323
|
daemonIcon,
|
|
47310
47324
|
agentStoragePath: path22.join(appostleHome, "agents"),
|