allagents 0.2.2 → 0.4.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 +53 -1
- package/dist/index.js +489 -90
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -60,6 +60,9 @@ bunx allagents
|
|
|
60
60
|
allagents workspace init my-workspace
|
|
61
61
|
cd my-workspace
|
|
62
62
|
|
|
63
|
+
# Or initialize from a remote GitHub template
|
|
64
|
+
allagents workspace init my-workspace --from owner/repo/path/to/template
|
|
65
|
+
|
|
63
66
|
# Add a marketplace (or let auto-registration handle it)
|
|
64
67
|
allagents plugin marketplace add anthropics/claude-plugins-official
|
|
65
68
|
|
|
@@ -71,6 +74,23 @@ allagents workspace plugin add my-plugin@someuser/their-repo
|
|
|
71
74
|
allagents workspace sync
|
|
72
75
|
```
|
|
73
76
|
|
|
77
|
+
### Initialize from Remote Template
|
|
78
|
+
|
|
79
|
+
Start a new workspace instantly from any GitHub repository containing a `workspace.yaml`:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# From GitHub URL
|
|
83
|
+
allagents workspace init ~/my-project --from https://github.com/myorg/templates/tree/main/nodejs
|
|
84
|
+
|
|
85
|
+
# From shorthand
|
|
86
|
+
allagents workspace init ~/my-project --from myorg/templates/nodejs
|
|
87
|
+
|
|
88
|
+
# From repo root (looks for .allagents/workspace.yaml or workspace.yaml)
|
|
89
|
+
allagents workspace init ~/my-project --from myorg/templates
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
This fetches the workspace configuration directly from GitHub - no cloning required.
|
|
93
|
+
|
|
74
94
|
## Commands
|
|
75
95
|
|
|
76
96
|
### Workspace Commands
|
|
@@ -78,6 +98,7 @@ allagents workspace sync
|
|
|
78
98
|
```bash
|
|
79
99
|
# Initialize a new workspace from template
|
|
80
100
|
allagents workspace init <path>
|
|
101
|
+
allagents workspace init <path> --from <source> # From local path or GitHub URL
|
|
81
102
|
|
|
82
103
|
# Sync all plugins to workspace (non-destructive)
|
|
83
104
|
allagents workspace sync [options]
|
|
@@ -130,9 +151,21 @@ allagents plugin validate <path>
|
|
|
130
151
|
|
|
131
152
|
## .allagents/workspace.yaml
|
|
132
153
|
|
|
133
|
-
The workspace configuration file lives in `.allagents/workspace.yaml` and defines repositories, plugins, and target clients:
|
|
154
|
+
The workspace configuration file lives in `.allagents/workspace.yaml` and defines repositories, plugins, workspace files, and target clients:
|
|
134
155
|
|
|
135
156
|
```yaml
|
|
157
|
+
# Workspace file sync (optional) - copy files from a shared source
|
|
158
|
+
workspace:
|
|
159
|
+
source: ../shared-config # Default base for relative paths
|
|
160
|
+
files:
|
|
161
|
+
- AGENTS.md # String shorthand: same source and dest
|
|
162
|
+
- source: docs/guide.md # Object form: explicit source
|
|
163
|
+
dest: GUIDE.md # Optional dest (defaults to basename)
|
|
164
|
+
- dest: CUSTOM.md # File-level source override
|
|
165
|
+
source: ../other-config/CUSTOM.md
|
|
166
|
+
- dest: AGENTS.md # GitHub source
|
|
167
|
+
source: owner/repo/path/AGENTS.md
|
|
168
|
+
|
|
136
169
|
repositories:
|
|
137
170
|
- path: ../my-project
|
|
138
171
|
owner: myorg
|
|
@@ -154,6 +187,25 @@ clients:
|
|
|
154
187
|
- cursor
|
|
155
188
|
```
|
|
156
189
|
|
|
190
|
+
### Workspace File Sync
|
|
191
|
+
|
|
192
|
+
The `workspace:` section enables syncing files from external sources to your workspace root. This is useful for sharing agent configurations (AGENTS.md, CLAUDE.md) across multiple projects.
|
|
193
|
+
|
|
194
|
+
**Key behaviors:**
|
|
195
|
+
- **Source of truth is remote** - Local copies are overwritten on every sync
|
|
196
|
+
- **Deleted files are restored** - If you delete AGENTS.md locally, sync restores it
|
|
197
|
+
- **WORKSPACE-RULES injection** - AGENTS.md and CLAUDE.md automatically get workspace discovery rules injected
|
|
198
|
+
|
|
199
|
+
**Source resolution:**
|
|
200
|
+
| Format | Example | Resolves to |
|
|
201
|
+
|--------|---------|-------------|
|
|
202
|
+
| String shorthand | `AGENTS.md` | `{workspace.source}/AGENTS.md` |
|
|
203
|
+
| Relative source | `source: docs/guide.md` | `{workspace.source}/docs/guide.md` |
|
|
204
|
+
| File-level override | `source: ../other/file.md` | `../other/file.md` (relative to workspace) |
|
|
205
|
+
| GitHub source | `source: owner/repo/path/file.md` | Fetched from GitHub cache |
|
|
206
|
+
|
|
207
|
+
**GitHub sources** are fetched fresh on every sync (always pulls latest).
|
|
208
|
+
|
|
157
209
|
### Plugin Spec Format
|
|
158
210
|
|
|
159
211
|
Plugins use the `plugin@marketplace` format:
|
package/dist/index.js
CHANGED
|
@@ -11054,9 +11054,9 @@ var {
|
|
|
11054
11054
|
} = import__.default;
|
|
11055
11055
|
|
|
11056
11056
|
// src/cli/index.ts
|
|
11057
|
-
import { readFileSync as
|
|
11058
|
-
import { dirname as
|
|
11059
|
-
import { fileURLToPath as
|
|
11057
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
11058
|
+
import { dirname as dirname7, join as join12 } from "node:path";
|
|
11059
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
11060
11060
|
|
|
11061
11061
|
// src/core/workspace.ts
|
|
11062
11062
|
import { mkdir as mkdir5, readFile as readFile6, writeFile as writeFile4, copyFile as copyFile2 } from "node:fs/promises";
|
|
@@ -17720,12 +17720,12 @@ var RepositorySchema = exports_external.object({
|
|
|
17720
17720
|
var WorkspaceFileSchema = exports_external.union([
|
|
17721
17721
|
exports_external.string(),
|
|
17722
17722
|
exports_external.object({
|
|
17723
|
-
source: exports_external.string(),
|
|
17723
|
+
source: exports_external.string().optional(),
|
|
17724
17724
|
dest: exports_external.string().optional()
|
|
17725
17725
|
})
|
|
17726
17726
|
]);
|
|
17727
17727
|
var WorkspaceSchema = exports_external.object({
|
|
17728
|
-
source: exports_external.string(),
|
|
17728
|
+
source: exports_external.string().optional(),
|
|
17729
17729
|
files: exports_external.array(WorkspaceFileSchema)
|
|
17730
17730
|
});
|
|
17731
17731
|
var PluginSourceSchema = exports_external.string();
|
|
@@ -19431,6 +19431,31 @@ function validatePluginSource(source) {
|
|
|
19431
19431
|
}
|
|
19432
19432
|
return { valid: true };
|
|
19433
19433
|
}
|
|
19434
|
+
function parseFileSource(source, baseDir = process.cwd()) {
|
|
19435
|
+
if (isGitHubUrl(source)) {
|
|
19436
|
+
const parsed = parseGitHubUrl(source);
|
|
19437
|
+
if (parsed) {
|
|
19438
|
+
return {
|
|
19439
|
+
type: "github",
|
|
19440
|
+
original: source,
|
|
19441
|
+
normalized: source,
|
|
19442
|
+
owner: parsed.owner,
|
|
19443
|
+
repo: parsed.repo,
|
|
19444
|
+
...parsed.subpath && { filePath: parsed.subpath }
|
|
19445
|
+
};
|
|
19446
|
+
}
|
|
19447
|
+
return {
|
|
19448
|
+
type: "local",
|
|
19449
|
+
original: source,
|
|
19450
|
+
normalized: normalizePluginPath(source, baseDir)
|
|
19451
|
+
};
|
|
19452
|
+
}
|
|
19453
|
+
return {
|
|
19454
|
+
type: "local",
|
|
19455
|
+
original: source,
|
|
19456
|
+
normalized: normalizePluginPath(source, baseDir)
|
|
19457
|
+
};
|
|
19458
|
+
}
|
|
19434
19459
|
async function verifyGitHubUrlExists(source) {
|
|
19435
19460
|
const parsed = parseGitHubUrl(source);
|
|
19436
19461
|
if (!parsed) {
|
|
@@ -19943,8 +19968,56 @@ async function copyPluginToWorkspace(pluginPath, workspacePath, client, options2
|
|
|
19943
19968
|
]);
|
|
19944
19969
|
return [...commandResults, ...skillResults, ...hookResults, ...agentResults];
|
|
19945
19970
|
}
|
|
19971
|
+
function isExplicitGitHubSource(source) {
|
|
19972
|
+
if (source.startsWith("https://github.com/") || source.startsWith("http://github.com/") || source.startsWith("github.com/") || source.startsWith("gh:")) {
|
|
19973
|
+
return true;
|
|
19974
|
+
}
|
|
19975
|
+
if (!source.startsWith(".") && !source.startsWith("/") && source.includes("/")) {
|
|
19976
|
+
const parts = source.split("/");
|
|
19977
|
+
if (parts.length >= 3) {
|
|
19978
|
+
const validOwnerRepo = /^[a-zA-Z0-9_.-]+$/;
|
|
19979
|
+
if (parts[0] && parts[1] && validOwnerRepo.test(parts[0]) && validOwnerRepo.test(parts[1])) {
|
|
19980
|
+
return true;
|
|
19981
|
+
}
|
|
19982
|
+
}
|
|
19983
|
+
}
|
|
19984
|
+
return false;
|
|
19985
|
+
}
|
|
19986
|
+
function resolveFileSourcePath(source, defaultSourcePath, githubCache) {
|
|
19987
|
+
if (!isExplicitGitHubSource(source)) {
|
|
19988
|
+
if (source.startsWith("/")) {
|
|
19989
|
+
return { path: source };
|
|
19990
|
+
}
|
|
19991
|
+
if (source.startsWith("../")) {
|
|
19992
|
+
return { path: join4(process.cwd(), source) };
|
|
19993
|
+
}
|
|
19994
|
+
if (defaultSourcePath) {
|
|
19995
|
+
return { path: join4(defaultSourcePath, source) };
|
|
19996
|
+
}
|
|
19997
|
+
return { path: join4(process.cwd(), source) };
|
|
19998
|
+
}
|
|
19999
|
+
const parsed = parseFileSource(source);
|
|
20000
|
+
if (parsed.type === "github" && parsed.owner && parsed.repo && parsed.filePath) {
|
|
20001
|
+
const cacheKey = `${parsed.owner}/${parsed.repo}`;
|
|
20002
|
+
const cachePath = githubCache?.get(cacheKey);
|
|
20003
|
+
if (!cachePath) {
|
|
20004
|
+
return {
|
|
20005
|
+
path: "",
|
|
20006
|
+
error: `GitHub cache not found for ${cacheKey}. Ensure the repo is fetched.`
|
|
20007
|
+
};
|
|
20008
|
+
}
|
|
20009
|
+
return { path: join4(cachePath, parsed.filePath) };
|
|
20010
|
+
}
|
|
20011
|
+
if (parsed.type === "github") {
|
|
20012
|
+
return {
|
|
20013
|
+
path: "",
|
|
20014
|
+
error: `Invalid GitHub file source: ${source}. Must include path to file (e.g., owner/repo/path/to/file.md)`
|
|
20015
|
+
};
|
|
20016
|
+
}
|
|
20017
|
+
return null;
|
|
20018
|
+
}
|
|
19946
20019
|
async function copyWorkspaceFiles(sourcePath, workspacePath, files, options2 = {}) {
|
|
19947
|
-
const { dryRun = false } = options2;
|
|
20020
|
+
const { dryRun = false, githubCache } = options2;
|
|
19948
20021
|
const results = [];
|
|
19949
20022
|
const stringPatterns = [];
|
|
19950
20023
|
const objectEntries = [];
|
|
@@ -19953,55 +20026,113 @@ async function copyWorkspaceFiles(sourcePath, workspacePath, files, options2 = {
|
|
|
19953
20026
|
if (typeof file === "string") {
|
|
19954
20027
|
stringPatterns.push(file);
|
|
19955
20028
|
} else {
|
|
19956
|
-
|
|
20029
|
+
let dest = file.dest;
|
|
20030
|
+
if (!dest && file.source) {
|
|
20031
|
+
const parts = file.source.split("/");
|
|
20032
|
+
dest = parts[parts.length - 1] || file.source;
|
|
20033
|
+
}
|
|
20034
|
+
if (!dest) {
|
|
20035
|
+
results.push({
|
|
20036
|
+
source: "unknown",
|
|
20037
|
+
destination: join4(workspacePath, "unknown"),
|
|
20038
|
+
action: "failed",
|
|
20039
|
+
error: "File entry must have at least source or dest specified"
|
|
20040
|
+
});
|
|
20041
|
+
continue;
|
|
20042
|
+
}
|
|
20043
|
+
objectEntries.push(file.source ? { source: file.source, dest } : { dest });
|
|
19957
20044
|
}
|
|
19958
20045
|
}
|
|
19959
20046
|
if (stringPatterns.length > 0) {
|
|
19960
|
-
|
|
19961
|
-
|
|
19962
|
-
|
|
19963
|
-
|
|
19964
|
-
|
|
19965
|
-
|
|
20047
|
+
if (!sourcePath) {
|
|
20048
|
+
for (const pattern of stringPatterns) {
|
|
20049
|
+
if (!isGlobPattern(pattern) && !pattern.startsWith("!")) {
|
|
20050
|
+
results.push({
|
|
20051
|
+
source: pattern,
|
|
20052
|
+
destination: join4(workspacePath, pattern),
|
|
20053
|
+
action: "failed",
|
|
20054
|
+
error: `Cannot resolve file '${pattern}' - no workspace.source configured and no explicit source provided`
|
|
20055
|
+
});
|
|
20056
|
+
}
|
|
20057
|
+
}
|
|
20058
|
+
} else {
|
|
20059
|
+
const resolvedFiles = await resolveGlobPatterns(sourcePath, stringPatterns);
|
|
20060
|
+
for (const resolved of resolvedFiles) {
|
|
20061
|
+
const destPath = join4(workspacePath, resolved.relativePath);
|
|
20062
|
+
if (!existsSync3(resolved.sourcePath)) {
|
|
20063
|
+
const wasLiteral = stringPatterns.some((p) => !isGlobPattern(p) && !p.startsWith("!") && p === resolved.relativePath);
|
|
20064
|
+
if (wasLiteral) {
|
|
20065
|
+
results.push({
|
|
20066
|
+
source: resolved.sourcePath,
|
|
20067
|
+
destination: destPath,
|
|
20068
|
+
action: "failed",
|
|
20069
|
+
error: `Source file not found: ${resolved.sourcePath}`
|
|
20070
|
+
});
|
|
20071
|
+
}
|
|
20072
|
+
continue;
|
|
20073
|
+
}
|
|
20074
|
+
if (dryRun) {
|
|
20075
|
+
results.push({ source: resolved.sourcePath, destination: destPath, action: "copied" });
|
|
20076
|
+
if (AGENT_FILES2.includes(resolved.relativePath)) {
|
|
20077
|
+
copiedAgentFiles.push(resolved.relativePath);
|
|
20078
|
+
}
|
|
20079
|
+
continue;
|
|
20080
|
+
}
|
|
20081
|
+
try {
|
|
20082
|
+
await mkdir2(dirname2(destPath), { recursive: true });
|
|
20083
|
+
const content = await readFile3(resolved.sourcePath, "utf-8");
|
|
20084
|
+
await writeFile(destPath, content, "utf-8");
|
|
20085
|
+
results.push({ source: resolved.sourcePath, destination: destPath, action: "copied" });
|
|
20086
|
+
if (AGENT_FILES2.includes(resolved.relativePath)) {
|
|
20087
|
+
copiedAgentFiles.push(resolved.relativePath);
|
|
20088
|
+
}
|
|
20089
|
+
} catch (error) {
|
|
19966
20090
|
results.push({
|
|
19967
20091
|
source: resolved.sourcePath,
|
|
19968
20092
|
destination: destPath,
|
|
19969
20093
|
action: "failed",
|
|
19970
|
-
error:
|
|
20094
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
19971
20095
|
});
|
|
19972
20096
|
}
|
|
20097
|
+
}
|
|
20098
|
+
}
|
|
20099
|
+
}
|
|
20100
|
+
for (const entry of objectEntries) {
|
|
20101
|
+
const destPath = join4(workspacePath, entry.dest);
|
|
20102
|
+
let srcPath;
|
|
20103
|
+
if (entry.source) {
|
|
20104
|
+
const resolved = resolveFileSourcePath(entry.source, sourcePath, githubCache);
|
|
20105
|
+
if (!resolved) {
|
|
20106
|
+
results.push({
|
|
20107
|
+
source: entry.source,
|
|
20108
|
+
destination: destPath,
|
|
20109
|
+
action: "failed",
|
|
20110
|
+
error: `Failed to resolve source: ${entry.source}`
|
|
20111
|
+
});
|
|
19973
20112
|
continue;
|
|
19974
20113
|
}
|
|
19975
|
-
if (
|
|
19976
|
-
results.push({
|
|
19977
|
-
|
|
19978
|
-
|
|
19979
|
-
|
|
20114
|
+
if (resolved.error) {
|
|
20115
|
+
results.push({
|
|
20116
|
+
source: entry.source,
|
|
20117
|
+
destination: destPath,
|
|
20118
|
+
action: "failed",
|
|
20119
|
+
error: resolved.error
|
|
20120
|
+
});
|
|
19980
20121
|
continue;
|
|
19981
20122
|
}
|
|
19982
|
-
|
|
19983
|
-
|
|
19984
|
-
|
|
19985
|
-
await writeFile(destPath, content, "utf-8");
|
|
19986
|
-
results.push({ source: resolved.sourcePath, destination: destPath, action: "copied" });
|
|
19987
|
-
if (AGENT_FILES2.includes(resolved.relativePath)) {
|
|
19988
|
-
copiedAgentFiles.push(resolved.relativePath);
|
|
19989
|
-
}
|
|
19990
|
-
} catch (error) {
|
|
20123
|
+
srcPath = resolved.path;
|
|
20124
|
+
} else {
|
|
20125
|
+
if (!sourcePath) {
|
|
19991
20126
|
results.push({
|
|
19992
|
-
source:
|
|
20127
|
+
source: entry.dest,
|
|
19993
20128
|
destination: destPath,
|
|
19994
20129
|
action: "failed",
|
|
19995
|
-
error:
|
|
20130
|
+
error: `Cannot resolve file '${entry.dest}' - no workspace.source configured and no explicit source provided`
|
|
19996
20131
|
});
|
|
20132
|
+
continue;
|
|
19997
20133
|
}
|
|
20134
|
+
srcPath = join4(sourcePath, entry.dest);
|
|
19998
20135
|
}
|
|
19999
|
-
}
|
|
20000
|
-
for (const entry of objectEntries) {
|
|
20001
|
-
const srcPath = join4(sourcePath, entry.source);
|
|
20002
|
-
const basename = entry.source.split("/").pop() || entry.source;
|
|
20003
|
-
const destFilename = entry.dest ?? basename;
|
|
20004
|
-
const destPath = join4(workspacePath, destFilename);
|
|
20005
20136
|
if (!existsSync3(srcPath)) {
|
|
20006
20137
|
results.push({
|
|
20007
20138
|
source: srcPath,
|
|
@@ -20013,8 +20144,8 @@ async function copyWorkspaceFiles(sourcePath, workspacePath, files, options2 = {
|
|
|
20013
20144
|
}
|
|
20014
20145
|
if (dryRun) {
|
|
20015
20146
|
results.push({ source: srcPath, destination: destPath, action: "copied" });
|
|
20016
|
-
if (AGENT_FILES2.includes(
|
|
20017
|
-
copiedAgentFiles.push(
|
|
20147
|
+
if (AGENT_FILES2.includes(entry.dest)) {
|
|
20148
|
+
copiedAgentFiles.push(entry.dest);
|
|
20018
20149
|
}
|
|
20019
20150
|
continue;
|
|
20020
20151
|
}
|
|
@@ -20023,8 +20154,8 @@ async function copyWorkspaceFiles(sourcePath, workspacePath, files, options2 = {
|
|
|
20023
20154
|
const content = await readFile3(srcPath, "utf-8");
|
|
20024
20155
|
await writeFile(destPath, content, "utf-8");
|
|
20025
20156
|
results.push({ source: srcPath, destination: destPath, action: "copied" });
|
|
20026
|
-
if (AGENT_FILES2.includes(
|
|
20027
|
-
copiedAgentFiles.push(
|
|
20157
|
+
if (AGENT_FILES2.includes(entry.dest)) {
|
|
20158
|
+
copiedAgentFiles.push(entry.dest);
|
|
20028
20159
|
}
|
|
20029
20160
|
} catch (error) {
|
|
20030
20161
|
results.push({
|
|
@@ -20451,6 +20582,130 @@ async function cleanupEmptyParents(workspacePath, filePath) {
|
|
|
20451
20582
|
}
|
|
20452
20583
|
}
|
|
20453
20584
|
}
|
|
20585
|
+
function isExplicitGitHubSourceForCollection(source) {
|
|
20586
|
+
if (source.startsWith("https://github.com/") || source.startsWith("http://github.com/") || source.startsWith("github.com/") || source.startsWith("gh:")) {
|
|
20587
|
+
return true;
|
|
20588
|
+
}
|
|
20589
|
+
if (!source.startsWith(".") && !source.startsWith("/") && source.includes("/")) {
|
|
20590
|
+
const parts = source.split("/");
|
|
20591
|
+
if (parts.length >= 3) {
|
|
20592
|
+
const validOwnerRepo = /^[a-zA-Z0-9_.-]+$/;
|
|
20593
|
+
if (parts[0] && parts[1] && validOwnerRepo.test(parts[0]) && validOwnerRepo.test(parts[1])) {
|
|
20594
|
+
return true;
|
|
20595
|
+
}
|
|
20596
|
+
}
|
|
20597
|
+
}
|
|
20598
|
+
return false;
|
|
20599
|
+
}
|
|
20600
|
+
function collectGitHubReposFromFiles(files) {
|
|
20601
|
+
const repos = new Map;
|
|
20602
|
+
for (const file of files) {
|
|
20603
|
+
if (typeof file === "string") {
|
|
20604
|
+
continue;
|
|
20605
|
+
}
|
|
20606
|
+
if (file.source && isExplicitGitHubSourceForCollection(file.source)) {
|
|
20607
|
+
const parsed = parseFileSource(file.source);
|
|
20608
|
+
if (parsed.type === "github" && parsed.owner && parsed.repo) {
|
|
20609
|
+
const key = `${parsed.owner}/${parsed.repo}`;
|
|
20610
|
+
if (!repos.has(key)) {
|
|
20611
|
+
repos.set(key, {
|
|
20612
|
+
owner: parsed.owner,
|
|
20613
|
+
repo: parsed.repo,
|
|
20614
|
+
key
|
|
20615
|
+
});
|
|
20616
|
+
}
|
|
20617
|
+
}
|
|
20618
|
+
}
|
|
20619
|
+
}
|
|
20620
|
+
return Array.from(repos.values());
|
|
20621
|
+
}
|
|
20622
|
+
async function fetchFileSourceRepos(repos, _force) {
|
|
20623
|
+
const cache = new Map;
|
|
20624
|
+
const errors2 = [];
|
|
20625
|
+
for (const repo of repos) {
|
|
20626
|
+
const result = await fetchPlugin(`${repo.owner}/${repo.repo}`, { force: true });
|
|
20627
|
+
if (result.success) {
|
|
20628
|
+
cache.set(repo.key, result.cachePath);
|
|
20629
|
+
} else {
|
|
20630
|
+
errors2.push(`Failed to fetch ${repo.key}: ${result.error || "Unknown error"}`);
|
|
20631
|
+
}
|
|
20632
|
+
}
|
|
20633
|
+
return { cache, errors: errors2 };
|
|
20634
|
+
}
|
|
20635
|
+
function isExplicitGitHubSourceForValidation(source) {
|
|
20636
|
+
if (source.startsWith("https://github.com/") || source.startsWith("http://github.com/") || source.startsWith("github.com/") || source.startsWith("gh:")) {
|
|
20637
|
+
return true;
|
|
20638
|
+
}
|
|
20639
|
+
if (!source.startsWith(".") && !source.startsWith("/") && source.includes("/")) {
|
|
20640
|
+
const parts = source.split("/");
|
|
20641
|
+
if (parts.length >= 3) {
|
|
20642
|
+
const validOwnerRepo = /^[a-zA-Z0-9_.-]+$/;
|
|
20643
|
+
if (parts[0] && parts[1] && validOwnerRepo.test(parts[0]) && validOwnerRepo.test(parts[1])) {
|
|
20644
|
+
return true;
|
|
20645
|
+
}
|
|
20646
|
+
}
|
|
20647
|
+
}
|
|
20648
|
+
return false;
|
|
20649
|
+
}
|
|
20650
|
+
function validateFileSources(files, defaultSourcePath, githubCache) {
|
|
20651
|
+
const errors2 = [];
|
|
20652
|
+
for (const file of files) {
|
|
20653
|
+
if (typeof file === "string") {
|
|
20654
|
+
if (!defaultSourcePath) {
|
|
20655
|
+
errors2.push(`Cannot resolve file '${file}' - no workspace.source configured`);
|
|
20656
|
+
continue;
|
|
20657
|
+
}
|
|
20658
|
+
const fullPath = join7(defaultSourcePath, file);
|
|
20659
|
+
if (!existsSync6(fullPath)) {
|
|
20660
|
+
errors2.push(`File source not found: ${fullPath}`);
|
|
20661
|
+
}
|
|
20662
|
+
continue;
|
|
20663
|
+
}
|
|
20664
|
+
if (file.source) {
|
|
20665
|
+
if (isExplicitGitHubSourceForValidation(file.source)) {
|
|
20666
|
+
const parsed = parseFileSource(file.source);
|
|
20667
|
+
if (!parsed.owner || !parsed.repo || !parsed.filePath) {
|
|
20668
|
+
errors2.push(`Invalid GitHub file source: ${file.source}. Must include path to file.`);
|
|
20669
|
+
continue;
|
|
20670
|
+
}
|
|
20671
|
+
const cacheKey = `${parsed.owner}/${parsed.repo}`;
|
|
20672
|
+
const cachePath = githubCache.get(cacheKey);
|
|
20673
|
+
if (!cachePath) {
|
|
20674
|
+
errors2.push(`GitHub cache not found for ${cacheKey}`);
|
|
20675
|
+
continue;
|
|
20676
|
+
}
|
|
20677
|
+
const fullPath = join7(cachePath, parsed.filePath);
|
|
20678
|
+
if (!existsSync6(fullPath)) {
|
|
20679
|
+
errors2.push(`Path not found in repository: ${cacheKey}/${parsed.filePath}`);
|
|
20680
|
+
}
|
|
20681
|
+
} else {
|
|
20682
|
+
let fullPath;
|
|
20683
|
+
if (file.source.startsWith("/")) {
|
|
20684
|
+
fullPath = file.source;
|
|
20685
|
+
} else if (file.source.startsWith("../")) {
|
|
20686
|
+
fullPath = resolve4(file.source);
|
|
20687
|
+
} else if (defaultSourcePath) {
|
|
20688
|
+
fullPath = join7(defaultSourcePath, file.source);
|
|
20689
|
+
} else {
|
|
20690
|
+
fullPath = resolve4(file.source);
|
|
20691
|
+
}
|
|
20692
|
+
if (!existsSync6(fullPath)) {
|
|
20693
|
+
errors2.push(`File source not found: ${fullPath}`);
|
|
20694
|
+
}
|
|
20695
|
+
}
|
|
20696
|
+
} else {
|
|
20697
|
+
if (!defaultSourcePath) {
|
|
20698
|
+
errors2.push(`Cannot resolve file '${file.dest}' - no workspace.source configured and no explicit source provided`);
|
|
20699
|
+
continue;
|
|
20700
|
+
}
|
|
20701
|
+
const fullPath = join7(defaultSourcePath, file.dest ?? "");
|
|
20702
|
+
if (!existsSync6(fullPath)) {
|
|
20703
|
+
errors2.push(`File source not found: ${fullPath}`);
|
|
20704
|
+
}
|
|
20705
|
+
}
|
|
20706
|
+
}
|
|
20707
|
+
return errors2;
|
|
20708
|
+
}
|
|
20454
20709
|
function collectSyncedPaths(copyResults, workspacePath, clients) {
|
|
20455
20710
|
const result = {};
|
|
20456
20711
|
for (const client of clients) {
|
|
@@ -20623,17 +20878,52 @@ ${errors2}`
|
|
|
20623
20878
|
}
|
|
20624
20879
|
const pluginResults = await Promise.all(validatedPlugins.map((validatedPlugin) => copyValidatedPlugin(validatedPlugin, workspacePath, config.clients, dryRun)));
|
|
20625
20880
|
let workspaceFileResults = [];
|
|
20626
|
-
if (config.workspace
|
|
20627
|
-
const sourcePath = validatedWorkspaceSource
|
|
20881
|
+
if (config.workspace) {
|
|
20882
|
+
const sourcePath = validatedWorkspaceSource?.resolved;
|
|
20628
20883
|
const filesToCopy = [...config.workspace.files];
|
|
20629
|
-
|
|
20630
|
-
const
|
|
20631
|
-
|
|
20632
|
-
filesToCopy.
|
|
20884
|
+
if (sourcePath) {
|
|
20885
|
+
for (const agentFile of AGENT_FILES) {
|
|
20886
|
+
const agentPath = join7(sourcePath, agentFile);
|
|
20887
|
+
if (existsSync6(agentPath) && !filesToCopy.includes(agentFile)) {
|
|
20888
|
+
filesToCopy.push(agentFile);
|
|
20889
|
+
}
|
|
20890
|
+
}
|
|
20891
|
+
}
|
|
20892
|
+
const fileSourceRepos = collectGitHubReposFromFiles(filesToCopy);
|
|
20893
|
+
let githubCache = new Map;
|
|
20894
|
+
if (fileSourceRepos.length > 0) {
|
|
20895
|
+
const { cache, errors: errors2 } = await fetchFileSourceRepos(fileSourceRepos, force);
|
|
20896
|
+
if (errors2.length > 0) {
|
|
20897
|
+
return {
|
|
20898
|
+
success: false,
|
|
20899
|
+
pluginResults,
|
|
20900
|
+
totalCopied: 0,
|
|
20901
|
+
totalFailed: errors2.length,
|
|
20902
|
+
totalSkipped: 0,
|
|
20903
|
+
totalGenerated: 0,
|
|
20904
|
+
error: `File source fetch failed (workspace unchanged):
|
|
20905
|
+
${errors2.map((e) => ` - ${e}`).join(`
|
|
20906
|
+
`)}`
|
|
20907
|
+
};
|
|
20633
20908
|
}
|
|
20909
|
+
githubCache = cache;
|
|
20634
20910
|
}
|
|
20635
|
-
|
|
20636
|
-
if (
|
|
20911
|
+
const fileValidationErrors = validateFileSources(filesToCopy, sourcePath, githubCache);
|
|
20912
|
+
if (fileValidationErrors.length > 0) {
|
|
20913
|
+
return {
|
|
20914
|
+
success: false,
|
|
20915
|
+
pluginResults,
|
|
20916
|
+
totalCopied: 0,
|
|
20917
|
+
totalFailed: fileValidationErrors.length,
|
|
20918
|
+
totalSkipped: 0,
|
|
20919
|
+
totalGenerated: 0,
|
|
20920
|
+
error: `File source validation failed (workspace unchanged):
|
|
20921
|
+
${fileValidationErrors.map((e) => ` - ${e}`).join(`
|
|
20922
|
+
`)}`
|
|
20923
|
+
};
|
|
20924
|
+
}
|
|
20925
|
+
workspaceFileResults = await copyWorkspaceFiles(sourcePath, workspacePath, filesToCopy, { dryRun, githubCache });
|
|
20926
|
+
if (!dryRun && config.clients.includes("claude") && sourcePath) {
|
|
20637
20927
|
const claudePath = join7(workspacePath, "CLAUDE.md");
|
|
20638
20928
|
const agentsPath = join7(workspacePath, "AGENTS.md");
|
|
20639
20929
|
const claudeExistsInSource = existsSync6(join7(sourcePath, "CLAUDE.md"));
|
|
@@ -20770,6 +21060,88 @@ async function autoRegisterMarketplace(name) {
|
|
|
20770
21060
|
};
|
|
20771
21061
|
}
|
|
20772
21062
|
|
|
21063
|
+
// src/core/github-fetch.ts
|
|
21064
|
+
async function fetchFileFromGitHub(owner, repo, path3) {
|
|
21065
|
+
try {
|
|
21066
|
+
const result = await execa("gh", [
|
|
21067
|
+
"api",
|
|
21068
|
+
`repos/${owner}/${repo}/contents/${path3}`,
|
|
21069
|
+
"--jq",
|
|
21070
|
+
".content"
|
|
21071
|
+
]);
|
|
21072
|
+
if (result.stdout) {
|
|
21073
|
+
const content = Buffer.from(result.stdout, "base64").toString("utf-8");
|
|
21074
|
+
return content;
|
|
21075
|
+
}
|
|
21076
|
+
return null;
|
|
21077
|
+
} catch {
|
|
21078
|
+
return null;
|
|
21079
|
+
}
|
|
21080
|
+
}
|
|
21081
|
+
async function fetchWorkspaceFromGitHub(url) {
|
|
21082
|
+
const parsed = parseGitHubUrl(url);
|
|
21083
|
+
if (!parsed) {
|
|
21084
|
+
return {
|
|
21085
|
+
success: false,
|
|
21086
|
+
error: "Invalid GitHub URL format. Expected: https://github.com/owner/repo"
|
|
21087
|
+
};
|
|
21088
|
+
}
|
|
21089
|
+
const { owner, repo, subpath } = parsed;
|
|
21090
|
+
try {
|
|
21091
|
+
await execa("gh", ["--version"]);
|
|
21092
|
+
} catch {
|
|
21093
|
+
return {
|
|
21094
|
+
success: false,
|
|
21095
|
+
error: "gh CLI not installed. Install from: https://cli.github.com"
|
|
21096
|
+
};
|
|
21097
|
+
}
|
|
21098
|
+
try {
|
|
21099
|
+
await execa("gh", ["repo", "view", `${owner}/${repo}`, "--json", "name"]);
|
|
21100
|
+
} catch (error) {
|
|
21101
|
+
if (error instanceof Error) {
|
|
21102
|
+
const errorMessage = error.message.toLowerCase();
|
|
21103
|
+
if (errorMessage.includes("not found") || errorMessage.includes("404") || errorMessage.includes("could not resolve to a repository")) {
|
|
21104
|
+
return {
|
|
21105
|
+
success: false,
|
|
21106
|
+
error: `Repository not found: ${owner}/${repo}`
|
|
21107
|
+
};
|
|
21108
|
+
}
|
|
21109
|
+
if (errorMessage.includes("auth") || errorMessage.includes("authentication")) {
|
|
21110
|
+
return {
|
|
21111
|
+
success: false,
|
|
21112
|
+
error: "GitHub authentication required. Run: gh auth login"
|
|
21113
|
+
};
|
|
21114
|
+
}
|
|
21115
|
+
}
|
|
21116
|
+
return {
|
|
21117
|
+
success: false,
|
|
21118
|
+
error: `Failed to access repository: ${error instanceof Error ? error.message : String(error)}`
|
|
21119
|
+
};
|
|
21120
|
+
}
|
|
21121
|
+
const basePath = subpath || "";
|
|
21122
|
+
const pathsToTry = basePath ? [
|
|
21123
|
+
`${basePath}/${CONFIG_DIR}/${WORKSPACE_CONFIG_FILE}`,
|
|
21124
|
+
`${basePath}/${WORKSPACE_CONFIG_FILE}`
|
|
21125
|
+
] : [
|
|
21126
|
+
`${CONFIG_DIR}/${WORKSPACE_CONFIG_FILE}`,
|
|
21127
|
+
WORKSPACE_CONFIG_FILE
|
|
21128
|
+
];
|
|
21129
|
+
for (const path3 of pathsToTry) {
|
|
21130
|
+
const content = await fetchFileFromGitHub(owner, repo, path3);
|
|
21131
|
+
if (content) {
|
|
21132
|
+
return {
|
|
21133
|
+
success: true,
|
|
21134
|
+
content
|
|
21135
|
+
};
|
|
21136
|
+
}
|
|
21137
|
+
}
|
|
21138
|
+
return {
|
|
21139
|
+
success: false,
|
|
21140
|
+
error: `No workspace.yaml found in: ${owner}/${repo}${subpath ? `/${subpath}` : ""}
|
|
21141
|
+
Expected at: ${pathsToTry.join(" or ")}`
|
|
21142
|
+
};
|
|
21143
|
+
}
|
|
21144
|
+
|
|
20773
21145
|
// src/core/workspace.ts
|
|
20774
21146
|
async function initWorkspace(targetPath = ".", options2 = {}) {
|
|
20775
21147
|
const absoluteTarget = resolve5(targetPath);
|
|
@@ -20789,48 +21161,72 @@ async function initWorkspace(targetPath = ".", options2 = {}) {
|
|
|
20789
21161
|
let workspaceYamlContent;
|
|
20790
21162
|
let sourceDir;
|
|
20791
21163
|
if (options2.from) {
|
|
20792
|
-
|
|
20793
|
-
|
|
20794
|
-
|
|
20795
|
-
|
|
20796
|
-
const { stat: stat2 } = await import("node:fs/promises");
|
|
20797
|
-
const fromStat = await stat2(fromPath);
|
|
20798
|
-
let sourceYamlPath;
|
|
20799
|
-
if (fromStat.isDirectory()) {
|
|
20800
|
-
const nestedPath = join8(fromPath, CONFIG_DIR, WORKSPACE_CONFIG_FILE);
|
|
20801
|
-
const rootPath = join8(fromPath, WORKSPACE_CONFIG_FILE);
|
|
20802
|
-
if (existsSync7(nestedPath)) {
|
|
20803
|
-
sourceYamlPath = nestedPath;
|
|
20804
|
-
sourceDir = fromPath;
|
|
20805
|
-
} else if (existsSync7(rootPath)) {
|
|
20806
|
-
sourceYamlPath = rootPath;
|
|
20807
|
-
sourceDir = fromPath;
|
|
20808
|
-
} else {
|
|
20809
|
-
throw new Error(`No workspace.yaml found in: ${fromPath}
|
|
20810
|
-
Expected at: ${nestedPath} or ${rootPath}`);
|
|
21164
|
+
if (isGitHubUrl(options2.from)) {
|
|
21165
|
+
const fetchResult = await fetchWorkspaceFromGitHub(options2.from);
|
|
21166
|
+
if (!fetchResult.success || !fetchResult.content) {
|
|
21167
|
+
throw new Error(fetchResult.error || "Failed to fetch workspace from GitHub");
|
|
20811
21168
|
}
|
|
20812
|
-
|
|
20813
|
-
sourceYamlPath = fromPath;
|
|
20814
|
-
const parentDir = dirname5(fromPath);
|
|
20815
|
-
if (parentDir.endsWith(CONFIG_DIR)) {
|
|
20816
|
-
sourceDir = dirname5(parentDir);
|
|
20817
|
-
} else {
|
|
20818
|
-
sourceDir = parentDir;
|
|
20819
|
-
}
|
|
20820
|
-
}
|
|
20821
|
-
workspaceYamlContent = await readFile6(sourceYamlPath, "utf-8");
|
|
20822
|
-
if (sourceDir) {
|
|
21169
|
+
workspaceYamlContent = fetchResult.content;
|
|
20823
21170
|
const parsed2 = load(workspaceYamlContent);
|
|
20824
21171
|
const workspace = parsed2?.workspace;
|
|
20825
21172
|
if (workspace?.source) {
|
|
20826
21173
|
const source = workspace.source;
|
|
20827
21174
|
if (!isGitHubUrl(source) && !isAbsolute2(source)) {
|
|
20828
|
-
|
|
20829
|
-
|
|
21175
|
+
const parsedUrl = parseGitHubUrl(options2.from);
|
|
21176
|
+
if (parsedUrl) {
|
|
21177
|
+
const basePath = parsedUrl.subpath || "";
|
|
21178
|
+
const baseDir = basePath.replace(/\/?\.allagents\/workspace\.yaml$/, "").replace(/\/?workspace\.yaml$/, "");
|
|
21179
|
+
const sourcePath = baseDir ? `${baseDir}/${source}` : source;
|
|
21180
|
+
workspace.source = `https://github.com/${parsedUrl.owner}/${parsedUrl.repo}/tree/main/${sourcePath}`;
|
|
21181
|
+
workspaceYamlContent = dump(parsed2, { lineWidth: -1 });
|
|
21182
|
+
}
|
|
20830
21183
|
}
|
|
20831
21184
|
}
|
|
21185
|
+
console.log(`✓ Using workspace.yaml from: ${options2.from}`);
|
|
21186
|
+
} else {
|
|
21187
|
+
const fromPath = resolve5(options2.from);
|
|
21188
|
+
if (!existsSync7(fromPath)) {
|
|
21189
|
+
throw new Error(`Template not found: ${fromPath}`);
|
|
21190
|
+
}
|
|
21191
|
+
const { stat: stat2 } = await import("node:fs/promises");
|
|
21192
|
+
const fromStat = await stat2(fromPath);
|
|
21193
|
+
let sourceYamlPath;
|
|
21194
|
+
if (fromStat.isDirectory()) {
|
|
21195
|
+
const nestedPath = join8(fromPath, CONFIG_DIR, WORKSPACE_CONFIG_FILE);
|
|
21196
|
+
const rootPath = join8(fromPath, WORKSPACE_CONFIG_FILE);
|
|
21197
|
+
if (existsSync7(nestedPath)) {
|
|
21198
|
+
sourceYamlPath = nestedPath;
|
|
21199
|
+
sourceDir = fromPath;
|
|
21200
|
+
} else if (existsSync7(rootPath)) {
|
|
21201
|
+
sourceYamlPath = rootPath;
|
|
21202
|
+
sourceDir = fromPath;
|
|
21203
|
+
} else {
|
|
21204
|
+
throw new Error(`No workspace.yaml found in: ${fromPath}
|
|
21205
|
+
Expected at: ${nestedPath} or ${rootPath}`);
|
|
21206
|
+
}
|
|
21207
|
+
} else {
|
|
21208
|
+
sourceYamlPath = fromPath;
|
|
21209
|
+
const parentDir = dirname5(fromPath);
|
|
21210
|
+
if (parentDir.endsWith(CONFIG_DIR)) {
|
|
21211
|
+
sourceDir = dirname5(parentDir);
|
|
21212
|
+
} else {
|
|
21213
|
+
sourceDir = parentDir;
|
|
21214
|
+
}
|
|
21215
|
+
}
|
|
21216
|
+
workspaceYamlContent = await readFile6(sourceYamlPath, "utf-8");
|
|
21217
|
+
if (sourceDir) {
|
|
21218
|
+
const parsed2 = load(workspaceYamlContent);
|
|
21219
|
+
const workspace = parsed2?.workspace;
|
|
21220
|
+
if (workspace?.source) {
|
|
21221
|
+
const source = workspace.source;
|
|
21222
|
+
if (!isGitHubUrl(source) && !isAbsolute2(source)) {
|
|
21223
|
+
workspace.source = resolve5(sourceDir, source);
|
|
21224
|
+
workspaceYamlContent = dump(parsed2, { lineWidth: -1 });
|
|
21225
|
+
}
|
|
21226
|
+
}
|
|
21227
|
+
}
|
|
21228
|
+
console.log(`✓ Using workspace.yaml from: ${sourceYamlPath}`);
|
|
20832
21229
|
}
|
|
20833
|
-
console.log(`✓ Using workspace.yaml from: ${sourceYamlPath}`);
|
|
20834
21230
|
} else {
|
|
20835
21231
|
const defaultYamlPath = join8(defaultTemplatePath, CONFIG_DIR, WORKSPACE_CONFIG_FILE);
|
|
20836
21232
|
if (!existsSync7(defaultYamlPath)) {
|
|
@@ -21457,6 +21853,9 @@ pluginCommand.command("validate <path>").description("Validate plugin structure
|
|
|
21457
21853
|
});
|
|
21458
21854
|
|
|
21459
21855
|
// src/cli/commands/update.ts
|
|
21856
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
21857
|
+
import { dirname as dirname6, join as join11 } from "node:path";
|
|
21858
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
21460
21859
|
function detectPackageManagerFromPath(scriptPath) {
|
|
21461
21860
|
if (scriptPath.includes(".bun")) {
|
|
21462
21861
|
return "bun";
|
|
@@ -21466,12 +21865,12 @@ function detectPackageManagerFromPath(scriptPath) {
|
|
|
21466
21865
|
function detectPackageManager() {
|
|
21467
21866
|
return detectPackageManagerFromPath(process.argv[1] ?? "");
|
|
21468
21867
|
}
|
|
21469
|
-
|
|
21868
|
+
function getCurrentVersion() {
|
|
21470
21869
|
try {
|
|
21471
|
-
const
|
|
21472
|
-
const
|
|
21473
|
-
const
|
|
21474
|
-
return
|
|
21870
|
+
const __dirname2 = dirname6(fileURLToPath3(import.meta.url));
|
|
21871
|
+
const packageJsonPath = join11(__dirname2, "..", "package.json");
|
|
21872
|
+
const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
|
|
21873
|
+
return packageJson.version;
|
|
21475
21874
|
} catch {
|
|
21476
21875
|
return "unknown";
|
|
21477
21876
|
}
|
|
@@ -21490,7 +21889,7 @@ var updateCommand = new Command("update").description("Update allagents to the l
|
|
|
21490
21889
|
} else {
|
|
21491
21890
|
packageManager = detectPackageManager();
|
|
21492
21891
|
}
|
|
21493
|
-
const currentVersion =
|
|
21892
|
+
const currentVersion = getCurrentVersion();
|
|
21494
21893
|
console.log(`Current version: ${currentVersion}`);
|
|
21495
21894
|
console.log(`Updating allagents using ${packageManager}...
|
|
21496
21895
|
`);
|
|
@@ -21525,9 +21924,9 @@ Update complete.`);
|
|
|
21525
21924
|
});
|
|
21526
21925
|
|
|
21527
21926
|
// src/cli/index.ts
|
|
21528
|
-
var __dirname2 =
|
|
21529
|
-
var packageJsonPath =
|
|
21530
|
-
var packageJson = JSON.parse(
|
|
21927
|
+
var __dirname2 = dirname7(fileURLToPath4(import.meta.url));
|
|
21928
|
+
var packageJsonPath = join12(__dirname2, "..", "package.json");
|
|
21929
|
+
var packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf-8"));
|
|
21531
21930
|
var program2 = new Command;
|
|
21532
21931
|
program2.name("allagents").description("CLI tool for managing multi-repo AI agent workspaces with plugin synchronization").version(packageJson.version);
|
|
21533
21932
|
program2.addCommand(workspaceCommand);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "allagents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "CLI tool for managing multi-repo AI agent workspaces with plugin synchronization",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"docs:install": "bun install --cwd docs",
|
|
15
15
|
"docs:build": "bun run --cwd docs build",
|
|
16
16
|
"dev": "bun run src/cli/index.ts",
|
|
17
|
-
"test": "bun test tests/unit/validators tests/unit/utils tests/unit/models && bun test tests/unit/core/marketplace.test.ts tests/unit/core/sync.test.ts tests/unit/core/transform-glob.test.ts && bun test tests/unit/core/plugin.test.ts",
|
|
17
|
+
"test": "bun test tests/unit/validators tests/unit/utils tests/unit/models && bun test tests/unit/core/marketplace.test.ts tests/unit/core/sync.test.ts tests/unit/core/transform-glob.test.ts tests/unit/core/github-fetch.test.ts && bun test tests/unit/core/plugin.test.ts",
|
|
18
18
|
"test:watch": "bun test --watch",
|
|
19
19
|
"test:coverage": "bun test --coverage",
|
|
20
20
|
"test:integration": "bats tests/integration/*.bats",
|