@iinm/plain-agent 1.8.6 → 1.8.8
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 +24 -53
- package/package.json +1 -1
- package/src/config.mjs +1 -1
- package/src/context/loadAgentRoles.mjs +1 -106
- package/src/context/loadPrompts.mjs +1 -106
package/README.md
CHANGED
|
@@ -29,9 +29,8 @@ A lightweight CLI-based coding agent.
|
|
|
29
29
|
|
|
30
30
|
- Node.js 22 or later
|
|
31
31
|
- LLM provider credentials
|
|
32
|
+
- [ripgrep](https://github.com/burntsushi/ripgrep), [fd](https://github.com/sharkdp/fd)
|
|
32
33
|
- Bash / Docker for sandboxed execution
|
|
33
|
-
- [ripgrep](https://github.com/burntsushi/ripgrep)
|
|
34
|
-
- [fd](https://github.com/sharkdp/fd)
|
|
35
34
|
|
|
36
35
|
## Quick Start
|
|
37
36
|
|
|
@@ -50,8 +49,8 @@ Create the configuration.
|
|
|
50
49
|
```js
|
|
51
50
|
// ~/.config/plain-agent/config.local.json
|
|
52
51
|
{
|
|
52
|
+
// Set default model
|
|
53
53
|
"model": "claude-sonnet-4-6+thinking-high",
|
|
54
|
-
// "model": "gpt-5.5+thinking-high",
|
|
55
54
|
|
|
56
55
|
// Configure the providers you want to use
|
|
57
56
|
"platforms": [
|
|
@@ -59,7 +58,7 @@ Create the configuration.
|
|
|
59
58
|
"name": "anthropic",
|
|
60
59
|
"variant": "default",
|
|
61
60
|
"apiKey": "<ANTHROPIC_API_KEY>"
|
|
62
|
-
// Or
|
|
61
|
+
// Or read from environment variable
|
|
63
62
|
// "apiKey": { "$env": "ANTHROPIC_API_KEY" }
|
|
64
63
|
},
|
|
65
64
|
{
|
|
@@ -71,17 +70,16 @@ Create the configuration.
|
|
|
71
70
|
"name": "openai",
|
|
72
71
|
"variant": "default",
|
|
73
72
|
"apiKey": "<OPENAI_API_KEY>"
|
|
74
|
-
}
|
|
73
|
+
}
|
|
75
74
|
],
|
|
76
75
|
|
|
77
|
-
// Optional
|
|
76
|
+
// (Optional) Enable web search tools
|
|
78
77
|
"tools": {
|
|
79
78
|
// askWeb: Searches the web to answer questions requiring up-to-date information or external sources.
|
|
80
79
|
"askWeb": {
|
|
81
80
|
"provider": "gemini",
|
|
82
81
|
"apiKey": "<GEMINI_API_KEY>",
|
|
83
82
|
"model": "gemini-3-flash-preview"
|
|
84
|
-
|
|
85
83
|
// Or use Vertex AI (Requires gcloud CLI to get authentication token)
|
|
86
84
|
// "provider": "gemini-vertex-ai",
|
|
87
85
|
// "baseURL": "https://aiplatform.googleapis.com/v1beta1/projects/<project_id>/locations/<location>",
|
|
@@ -89,18 +87,14 @@ Create the configuration.
|
|
|
89
87
|
},
|
|
90
88
|
|
|
91
89
|
// askURL: Answers questions based on provided URL content.
|
|
92
|
-
// Directly injecting URL content into context is not supported to prevent prompt injection.
|
|
93
90
|
"askURL": {
|
|
94
91
|
"provider": "gemini",
|
|
95
|
-
"apiKey": "<GEMINI_API_KEY>"
|
|
92
|
+
"apiKey": "<GEMINI_API_KEY>",
|
|
96
93
|
"model": "gemini-3-flash-preview"
|
|
97
|
-
|
|
98
94
|
// Or use Vertex AI (Requires gcloud CLI to get authentication token)
|
|
99
95
|
}
|
|
100
|
-
}
|
|
101
|
-
|
|
96
|
+
}
|
|
102
97
|
}
|
|
103
|
-
|
|
104
98
|
```
|
|
105
99
|
|
|
106
100
|
<details>
|
|
@@ -118,6 +112,7 @@ Create the configuration.
|
|
|
118
112
|
"azureConfigDir": "/home/xxx/.azure-for-agent"
|
|
119
113
|
},
|
|
120
114
|
{
|
|
115
|
+
// Requires AWS CLI to get credentials
|
|
121
116
|
"name": "bedrock",
|
|
122
117
|
"variant": "default",
|
|
123
118
|
"baseURL": "https://bedrock-runtime.<region>.amazonaws.com",
|
|
@@ -128,8 +123,8 @@ Create the configuration.
|
|
|
128
123
|
"name": "vertex-ai",
|
|
129
124
|
"variant": "default",
|
|
130
125
|
"baseURL": "https://aiplatform.googleapis.com/v1beta1/projects/<project>/locations/<location>",
|
|
131
|
-
// Optional
|
|
132
|
-
"account": "<
|
|
126
|
+
// (Optional) Impersonate this service account to obtain an auth token
|
|
127
|
+
"account": "<SERVICE_ACCOUNT_EMAIL>"
|
|
133
128
|
}
|
|
134
129
|
]
|
|
135
130
|
}
|
|
@@ -302,7 +297,7 @@ plain cost
|
|
|
302
297
|
plain cost --from 2026-04-01 --to 2026-04-30
|
|
303
298
|
```
|
|
304
299
|
|
|
305
|
-
|
|
300
|
+
Configure plain-agent for your project.
|
|
306
301
|
|
|
307
302
|
```
|
|
308
303
|
/configure Auto-approve file writes and patches
|
|
@@ -417,14 +412,13 @@ Files are loaded in the following order. Settings in later files override earlie
|
|
|
417
412
|
|
|
418
413
|
// MCP Tool naming convention: mcp__<serverName>__<toolName>
|
|
419
414
|
{
|
|
420
|
-
"toolName": { "$regex": "
|
|
415
|
+
"toolName": { "$regex": "mcp__slack__slack_(read|search)_.+" },
|
|
421
416
|
"action": "allow"
|
|
422
417
|
}
|
|
423
418
|
]
|
|
424
419
|
},
|
|
425
420
|
|
|
426
|
-
//
|
|
427
|
-
// https://github.com/iinm/plain-agent/tree/main/sandbox
|
|
421
|
+
// Sandbox environment for the exec_command and tmux_command tools
|
|
428
422
|
"sandbox": {
|
|
429
423
|
"command": "plain-sandbox",
|
|
430
424
|
"args": ["--allow-write", "--skip-build", "--keep-alive", "30"],
|
|
@@ -458,7 +452,7 @@ Files are loaded in the following order. Settings in later files override earlie
|
|
|
458
452
|
"command": "npx",
|
|
459
453
|
"args": ["-y", "chrome-devtools-mcp@latest", "--isolated"]
|
|
460
454
|
},
|
|
461
|
-
// ⚠️ Add
|
|
455
|
+
// ⚠️ Add to config.local.json to avoid committing secrets to Git
|
|
462
456
|
"slack": {
|
|
463
457
|
"command": "npx",
|
|
464
458
|
"args": ["-y", "mcp-remote", "https://mcp.slack.com/mcp", "--header", "Authorization:Bearer <SLACK_TOKEN>"],
|
|
@@ -475,21 +469,22 @@ Files are loaded in the following order. Settings in later files override earlie
|
|
|
475
469
|
"command": "npx",
|
|
476
470
|
"args": ["-y", "mcp-remote", "https://knowledge-mcp.global.api.aws"]
|
|
477
471
|
},
|
|
478
|
-
// ⚠️ Add
|
|
472
|
+
// ⚠️ Add to config.local.json to avoid committing secrets to Git
|
|
479
473
|
"google_developer-knowledge": {
|
|
480
474
|
"command": "npx",
|
|
481
475
|
"args": ["-y", "mcp-remote", "https://developerknowledge.googleapis.com/mcp", "--header", "X-Goog-Api-Key:<GOOGLE_API_KEY>"]
|
|
482
476
|
}
|
|
483
477
|
},
|
|
484
478
|
|
|
485
|
-
// Override default notification command
|
|
486
|
-
|
|
479
|
+
// Override default notification command
|
|
480
|
+
"notifyCmd": { "command": "plain-notify-desktop", "args": [] }
|
|
487
481
|
|
|
488
|
-
//
|
|
489
|
-
//
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
482
|
+
// Voice input. See "Voice Input" below.
|
|
483
|
+
// ⚠️ Add to config.local.json to avoid committing secrets to Git
|
|
484
|
+
"voiceInput": {
|
|
485
|
+
"provider": "openai",
|
|
486
|
+
"apiKey": "<OPENAI_API_KEY>"
|
|
487
|
+
}
|
|
493
488
|
}
|
|
494
489
|
```
|
|
495
490
|
</details>
|
|
@@ -518,6 +513,7 @@ The agent searches for prompts in the following directories:
|
|
|
518
513
|
|
|
519
514
|
- `~/.config/plain-agent/prompts/`
|
|
520
515
|
- `.plain-agent/prompts/`
|
|
516
|
+
- `.plain-agent/prompts/skills/`
|
|
521
517
|
- `.claude/commands/`
|
|
522
518
|
- `.claude/skills/`
|
|
523
519
|
|
|
@@ -533,19 +529,6 @@ description: Create a commit message based on staged changes
|
|
|
533
529
|
Review the staged changes and create a concise commit message following the conventional commits specification.
|
|
534
530
|
```
|
|
535
531
|
|
|
536
|
-
You can also import remote prompts with the `import` field:
|
|
537
|
-
|
|
538
|
-
```md
|
|
539
|
-
---
|
|
540
|
-
import: https://raw.githubusercontent.com/anthropics/claude-code/5cff78741f54a0dcfaeb11d29b9ea9a83f3882ff/plugins/feature-dev/commands/feature-dev.md
|
|
541
|
-
---
|
|
542
|
-
|
|
543
|
-
- Use memory file instead of TodoWrite
|
|
544
|
-
- Parallel execution of subagents is not supported. Delegate to subagents sequentially.
|
|
545
|
-
```
|
|
546
|
-
|
|
547
|
-
Remote prompts are fetched and cached locally. The local content will be appended to the imported content.
|
|
548
|
-
|
|
549
532
|
### Shortcuts
|
|
550
533
|
|
|
551
534
|
Prompts located in a `shortcuts/` subdirectory (e.g., `.plain-agent/prompts/shortcuts/commit.md`) can be invoked directly as a top-level command (e.g., `/commit`).
|
|
@@ -572,18 +555,6 @@ description: Simplifies and refines code for clarity and maintainability
|
|
|
572
555
|
You are a code simplifier. Your role is to refactor code while preserving its functionality.
|
|
573
556
|
```
|
|
574
557
|
|
|
575
|
-
You can also import remote subagent definitions with the `import` field:
|
|
576
|
-
|
|
577
|
-
```md
|
|
578
|
-
---
|
|
579
|
-
import: https://raw.githubusercontent.com/anthropics/claude-code/f7ab5c799caf2ec8c7cd1b99d2bc2f158459ef5e/plugins/pr-review-toolkit/agents/code-simplifier.md
|
|
580
|
-
---
|
|
581
|
-
|
|
582
|
-
Use AGENTS.md instead of CLAUDE.md in this project.
|
|
583
|
-
```
|
|
584
|
-
|
|
585
|
-
Remote subagents are fetched and cached locally. The local content will be appended to the imported content.
|
|
586
|
-
|
|
587
558
|
## Claude Code Plugin Support
|
|
588
559
|
|
|
589
560
|
Example:
|
package/package.json
CHANGED
package/src/config.mjs
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
/** @import { ClaudeCodePlugin } from "../claudeCodePlugin.mjs" */
|
|
2
2
|
|
|
3
|
-
import crypto from "node:crypto";
|
|
4
3
|
import fs from "node:fs/promises";
|
|
5
4
|
import path from "node:path";
|
|
6
5
|
import {
|
|
7
|
-
AGENT_CACHE_DIR,
|
|
8
6
|
AGENT_PROJECT_METADATA_DIR,
|
|
9
7
|
AGENT_ROOT,
|
|
10
8
|
AGENT_USER_CONFIG_DIR,
|
|
@@ -18,7 +16,6 @@ import { parseFrontmatter } from "../utils/parseFrontmatter.mjs";
|
|
|
18
16
|
* @property {string} content
|
|
19
17
|
* @property {string} filePath
|
|
20
18
|
* @property {boolean} claudeOriginated
|
|
21
|
-
* @property {string} [import]
|
|
22
19
|
*/
|
|
23
20
|
|
|
24
21
|
/**
|
|
@@ -81,15 +78,7 @@ export async function loadAgentRoles(claudeCodePlugins) {
|
|
|
81
78
|
|
|
82
79
|
if (content === null) return null;
|
|
83
80
|
|
|
84
|
-
|
|
85
|
-
if (role.import) {
|
|
86
|
-
try {
|
|
87
|
-
role = await mergeRemoteRole(role, file, fullPath);
|
|
88
|
-
} catch (err) {
|
|
89
|
-
console.warn(`Failed to import remote role ${role.id}:`, err);
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
81
|
+
const role = parseAgentRole(file, content, fullPath, idPrefix);
|
|
93
82
|
|
|
94
83
|
return role;
|
|
95
84
|
}),
|
|
@@ -100,99 +89,6 @@ export async function loadAgentRoles(claudeCodePlugins) {
|
|
|
100
89
|
return new Map(roles.map((role) => [role.id, role]));
|
|
101
90
|
}
|
|
102
91
|
|
|
103
|
-
/**
|
|
104
|
-
* Merges a remote role into a local role.
|
|
105
|
-
* @param {AgentRole} localRole
|
|
106
|
-
* @param {string} relativePath
|
|
107
|
-
* @param {string} fullPath
|
|
108
|
-
* @returns {Promise<AgentRole>}
|
|
109
|
-
*/
|
|
110
|
-
async function mergeRemoteRole(localRole, relativePath, fullPath) {
|
|
111
|
-
const importUrl = localRole.import;
|
|
112
|
-
if (!importUrl) {
|
|
113
|
-
return localRole;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const fetchedContent = await fetchAndCacheRole(importUrl).catch((err) => {
|
|
117
|
-
console.warn(`Failed to fetch agent role from ${importUrl}:`, err);
|
|
118
|
-
return null;
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
if (!fetchedContent) {
|
|
122
|
-
return localRole;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const remoteRole = parseAgentRole(relativePath, fetchedContent, fullPath);
|
|
126
|
-
|
|
127
|
-
return {
|
|
128
|
-
...remoteRole,
|
|
129
|
-
...localRole, // Local overrides
|
|
130
|
-
content: `${remoteRole.content}\n\n---\n\n${localRole.content}`.trim(),
|
|
131
|
-
description: localRole.description || remoteRole.description || "",
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Fetch an agent role from a URL and cache it.
|
|
137
|
-
* @param {string} url
|
|
138
|
-
* @returns {Promise<string>}
|
|
139
|
-
*/
|
|
140
|
-
async function fetchAndCacheRole(url) {
|
|
141
|
-
const hash = crypto.createHash("sha256").update(url).digest("hex");
|
|
142
|
-
const cacheDir = path.join(AGENT_CACHE_DIR, "agents");
|
|
143
|
-
const cachePath = path.join(cacheDir, hash);
|
|
144
|
-
|
|
145
|
-
const cachedContent = await fs.readFile(cachePath, "utf-8").catch(() => null);
|
|
146
|
-
if (cachedContent !== null) {
|
|
147
|
-
return cachedContent;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const fetchedContent = await fetchContent(url);
|
|
151
|
-
|
|
152
|
-
// Attempt to cache, but don't block or fail on errors
|
|
153
|
-
fs.mkdir(cacheDir, { recursive: true })
|
|
154
|
-
.then(() => fs.writeFile(cachePath, fetchedContent, "utf-8"))
|
|
155
|
-
.catch((err) => {
|
|
156
|
-
console.warn(`Failed to write cache for ${url}:`, err);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
return fetchedContent;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Fetch content from a URL.
|
|
164
|
-
* @param {string} url
|
|
165
|
-
* @returns {Promise<string>}
|
|
166
|
-
*/
|
|
167
|
-
async function fetchContent(url) {
|
|
168
|
-
const githubMatch = url.match(
|
|
169
|
-
/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/blob\/([^/]+)\/(.+)$/,
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
if (githubMatch) {
|
|
173
|
-
const [, owner, repo, ref, path] = githubMatch;
|
|
174
|
-
const apiUrl = `repos/${owner}/${repo}/contents/${path}?ref=${ref}`;
|
|
175
|
-
try {
|
|
176
|
-
const { execFileSync } = await import("node:child_process");
|
|
177
|
-
return execFileSync(
|
|
178
|
-
"gh",
|
|
179
|
-
["api", "-H", "Accept: application/vnd.github.v3.raw", apiUrl],
|
|
180
|
-
{ encoding: "utf-8" },
|
|
181
|
-
);
|
|
182
|
-
} catch (err) {
|
|
183
|
-
throw new Error(`Failed to fetch from GitHub via gh CLI: ${err}`);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const response = await fetch(url);
|
|
188
|
-
if (!response.ok) {
|
|
189
|
-
throw new Error(
|
|
190
|
-
`Failed to fetch agent role from ${url}: ${response.status} ${response.statusText}`,
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
return response.text();
|
|
194
|
-
}
|
|
195
|
-
|
|
196
92
|
/**
|
|
197
93
|
* Recursively get all markdown files in a directory.
|
|
198
94
|
* @param {string} dir
|
|
@@ -262,6 +158,5 @@ function parseAgentRole(relativePath, fileContent, fullPath, idPrefix = "") {
|
|
|
262
158
|
content,
|
|
263
159
|
filePath: fullPath,
|
|
264
160
|
claudeOriginated,
|
|
265
|
-
import: frontmatter.import,
|
|
266
161
|
};
|
|
267
162
|
}
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
/** @import { ClaudeCodePlugin } from "../claudeCodePlugin.mjs" */
|
|
2
2
|
|
|
3
|
-
import { execFileSync } from "node:child_process";
|
|
4
|
-
import crypto from "node:crypto";
|
|
5
3
|
import fs from "node:fs/promises";
|
|
6
4
|
import path from "node:path";
|
|
7
5
|
import {
|
|
8
|
-
AGENT_CACHE_DIR,
|
|
9
6
|
AGENT_PROJECT_METADATA_DIR,
|
|
10
7
|
AGENT_ROOT,
|
|
11
8
|
AGENT_USER_CONFIG_DIR,
|
|
@@ -19,7 +16,6 @@ import { parseFrontmatter } from "../utils/parseFrontmatter.mjs";
|
|
|
19
16
|
* @property {string} content
|
|
20
17
|
* @property {string} filePath
|
|
21
18
|
* @property {boolean} claudeOriginated
|
|
22
|
-
* @property {string} [import]
|
|
23
19
|
* @property {boolean} [userInvocable]
|
|
24
20
|
* @property {boolean} [isShortcut]
|
|
25
21
|
* @property {boolean} [isSkill]
|
|
@@ -105,15 +101,7 @@ export async function loadPrompts(claudeCodePlugins) {
|
|
|
105
101
|
|
|
106
102
|
if (content === null) return null;
|
|
107
103
|
|
|
108
|
-
|
|
109
|
-
if (prompt.import) {
|
|
110
|
-
try {
|
|
111
|
-
prompt = await mergeRemotePrompt(prompt, file, fullPath);
|
|
112
|
-
} catch (err) {
|
|
113
|
-
console.warn(`Failed to import remote prompt ${prompt.id}:`, err);
|
|
114
|
-
return null;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
104
|
+
const prompt = parsePrompt(file, content, fullPath, idPrefix);
|
|
117
105
|
|
|
118
106
|
if (prompt.userInvocable === false) {
|
|
119
107
|
return null;
|
|
@@ -127,98 +115,6 @@ export async function loadPrompts(claudeCodePlugins) {
|
|
|
127
115
|
return new Map(prompts.map((prompt) => [prompt.id, prompt]));
|
|
128
116
|
}
|
|
129
117
|
|
|
130
|
-
/**
|
|
131
|
-
* Merges a remote prompt into a local prompt if an import URL is provided.
|
|
132
|
-
* @param {Prompt} localPrompt
|
|
133
|
-
* @param {string} relativePath
|
|
134
|
-
* @param {string} fullPath
|
|
135
|
-
* @returns {Promise<Prompt>}
|
|
136
|
-
*/
|
|
137
|
-
async function mergeRemotePrompt(localPrompt, relativePath, fullPath) {
|
|
138
|
-
const importUrl = localPrompt.import;
|
|
139
|
-
if (!importUrl) {
|
|
140
|
-
return localPrompt;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const fetchedContent = await fetchAndCachePrompt(importUrl).catch((err) => {
|
|
144
|
-
console.warn(`Failed to fetch prompt from ${importUrl}:`, err);
|
|
145
|
-
return null;
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
if (!fetchedContent) {
|
|
149
|
-
return localPrompt;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const remotePrompt = parsePrompt(relativePath, fetchedContent, fullPath);
|
|
153
|
-
|
|
154
|
-
return {
|
|
155
|
-
...remotePrompt,
|
|
156
|
-
...localPrompt, // Local overrides
|
|
157
|
-
content: `${remotePrompt.content}\n\n---\n\n${localPrompt.content}`.trim(),
|
|
158
|
-
description: localPrompt.description || remotePrompt.description || "",
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Fetch a prompt from a URL and cache it.
|
|
164
|
-
* @param {string} url
|
|
165
|
-
* @returns {Promise<string>}
|
|
166
|
-
*/
|
|
167
|
-
async function fetchAndCachePrompt(url) {
|
|
168
|
-
const hash = crypto.createHash("sha256").update(url).digest("hex");
|
|
169
|
-
const cacheDir = path.join(AGENT_CACHE_DIR, "prompts");
|
|
170
|
-
const cachePath = path.join(cacheDir, hash);
|
|
171
|
-
|
|
172
|
-
const cachedContent = await fs.readFile(cachePath, "utf-8").catch(() => null);
|
|
173
|
-
if (cachedContent !== null) {
|
|
174
|
-
return cachedContent;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const fetchedContent = await fetchContent(url);
|
|
178
|
-
|
|
179
|
-
// Attempt to cache, but don't block or fail on errors
|
|
180
|
-
fs.mkdir(cacheDir, { recursive: true })
|
|
181
|
-
.then(() => fs.writeFile(cachePath, fetchedContent, "utf-8"))
|
|
182
|
-
.catch((err) => {
|
|
183
|
-
console.warn(`Failed to write cache for ${url}:`, err);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
return fetchedContent;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Fetch content from a URL.
|
|
191
|
-
* @param {string} url
|
|
192
|
-
* @returns {Promise<string>}
|
|
193
|
-
*/
|
|
194
|
-
async function fetchContent(url) {
|
|
195
|
-
const githubMatch = url.match(
|
|
196
|
-
/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/blob\/([^/]+)\/(.+)$/,
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
if (githubMatch) {
|
|
200
|
-
const [, owner, repo, ref, path] = githubMatch;
|
|
201
|
-
const apiUrl = `repos/${owner}/${repo}/contents/${path}?ref=${ref}`;
|
|
202
|
-
try {
|
|
203
|
-
return execFileSync(
|
|
204
|
-
"gh",
|
|
205
|
-
["api", "-H", "Accept: application/vnd.github.v3.raw", apiUrl],
|
|
206
|
-
{ encoding: "utf-8" },
|
|
207
|
-
);
|
|
208
|
-
} catch (err) {
|
|
209
|
-
throw new Error(`Failed to fetch from GitHub via gh CLI: ${err}`);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const response = await fetch(url);
|
|
214
|
-
if (!response.ok) {
|
|
215
|
-
throw new Error(
|
|
216
|
-
`Failed to fetch prompt from ${url}: ${response.status} ${response.statusText}`,
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
return response.text();
|
|
220
|
-
}
|
|
221
|
-
|
|
222
118
|
/**
|
|
223
119
|
* Recursively get all markdown files in a directory.
|
|
224
120
|
* @param {string} dir
|
|
@@ -295,7 +191,6 @@ function parsePrompt(relativePath, fileContent, fullPath, idPrefix = "") {
|
|
|
295
191
|
content,
|
|
296
192
|
filePath: fullPath,
|
|
297
193
|
claudeOriginated,
|
|
298
|
-
import: frontmatter.import,
|
|
299
194
|
userInvocable: frontmatter["user-invocable"] === "true" ? true : undefined,
|
|
300
195
|
isShortcut,
|
|
301
196
|
isSkill: relativePath.endsWith("SKILL.md"),
|