allagents 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -1
- package/dist/index.js +438 -84
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -130,9 +130,21 @@ allagents plugin validate <path>
|
|
|
130
130
|
|
|
131
131
|
## .allagents/workspace.yaml
|
|
132
132
|
|
|
133
|
-
The workspace configuration file lives in `.allagents/workspace.yaml` and defines repositories, plugins, and target clients:
|
|
133
|
+
The workspace configuration file lives in `.allagents/workspace.yaml` and defines repositories, plugins, workspace files, and target clients:
|
|
134
134
|
|
|
135
135
|
```yaml
|
|
136
|
+
# Workspace file sync (optional) - copy files from a shared source
|
|
137
|
+
workspace:
|
|
138
|
+
source: ../shared-config # Default base for relative paths
|
|
139
|
+
files:
|
|
140
|
+
- AGENTS.md # String shorthand: same source and dest
|
|
141
|
+
- source: docs/guide.md # Object form: explicit source
|
|
142
|
+
dest: GUIDE.md # Optional dest (defaults to basename)
|
|
143
|
+
- dest: CUSTOM.md # File-level source override
|
|
144
|
+
source: ../other-config/CUSTOM.md
|
|
145
|
+
- dest: AGENTS.md # GitHub source
|
|
146
|
+
source: owner/repo/path/AGENTS.md
|
|
147
|
+
|
|
136
148
|
repositories:
|
|
137
149
|
- path: ../my-project
|
|
138
150
|
owner: myorg
|
|
@@ -154,6 +166,25 @@ clients:
|
|
|
154
166
|
- cursor
|
|
155
167
|
```
|
|
156
168
|
|
|
169
|
+
### Workspace File Sync
|
|
170
|
+
|
|
171
|
+
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.
|
|
172
|
+
|
|
173
|
+
**Key behaviors:**
|
|
174
|
+
- **Source of truth is remote** - Local copies are overwritten on every sync
|
|
175
|
+
- **Deleted files are restored** - If you delete AGENTS.md locally, sync restores it
|
|
176
|
+
- **WORKSPACE-RULES injection** - AGENTS.md and CLAUDE.md automatically get workspace discovery rules injected
|
|
177
|
+
|
|
178
|
+
**Source resolution:**
|
|
179
|
+
| Format | Example | Resolves to |
|
|
180
|
+
|--------|---------|-------------|
|
|
181
|
+
| String shorthand | `AGENTS.md` | `{workspace.source}/AGENTS.md` |
|
|
182
|
+
| Relative source | `source: docs/guide.md` | `{workspace.source}/docs/guide.md` |
|
|
183
|
+
| File-level override | `source: ../other/file.md` | `../other/file.md` (relative to workspace) |
|
|
184
|
+
| GitHub source | `source: owner/repo/path/file.md` | Fetched from GitHub cache |
|
|
185
|
+
|
|
186
|
+
**GitHub sources** are fetched fresh on every sync (always pulls latest).
|
|
187
|
+
|
|
157
188
|
### Plugin Spec Format
|
|
158
189
|
|
|
159
190
|
Plugins use the `plugin@marketplace` format:
|
package/dist/index.js
CHANGED
|
@@ -11059,36 +11059,11 @@ import { dirname as dirname6, join as join11 } from "node:path";
|
|
|
11059
11059
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
11060
11060
|
|
|
11061
11061
|
// src/core/workspace.ts
|
|
11062
|
-
import { mkdir as mkdir5, readFile as readFile6, writeFile as writeFile4 } from "node:fs/promises";
|
|
11062
|
+
import { mkdir as mkdir5, readFile as readFile6, writeFile as writeFile4, copyFile as copyFile2 } from "node:fs/promises";
|
|
11063
11063
|
import { existsSync as existsSync7 } from "node:fs";
|
|
11064
|
-
import { join as join8, resolve as resolve5, dirname as dirname5, relative as relative2, sep } from "node:path";
|
|
11064
|
+
import { join as join8, resolve as resolve5, dirname as dirname5, relative as relative2, sep, isAbsolute as isAbsolute2 } from "node:path";
|
|
11065
11065
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
11066
11066
|
|
|
11067
|
-
// src/core/sync.ts
|
|
11068
|
-
import { existsSync as existsSync6 } from "node:fs";
|
|
11069
|
-
import { rm, unlink, rmdir } from "node:fs/promises";
|
|
11070
|
-
import { join as join7, resolve as resolve4, dirname as dirname4, relative } from "node:path";
|
|
11071
|
-
|
|
11072
|
-
// src/constants.ts
|
|
11073
|
-
var CONFIG_DIR = ".allagents";
|
|
11074
|
-
var SYNC_STATE_FILE = "sync-state.json";
|
|
11075
|
-
var WORKSPACE_CONFIG_FILE = "workspace.yaml";
|
|
11076
|
-
var WORKSPACE_CONFIG_PATH = `${CONFIG_DIR}/${WORKSPACE_CONFIG_FILE}`;
|
|
11077
|
-
var WORKSPACE_RULES = `
|
|
11078
|
-
<!-- WORKSPACE-RULES:START -->
|
|
11079
|
-
## Rule: Workspace Discovery
|
|
11080
|
-
TRIGGER: Any task
|
|
11081
|
-
ACTION: Read \`.allagents/workspace.yaml\` to get repository paths and project domains
|
|
11082
|
-
|
|
11083
|
-
## Rule: Correct Repository Paths
|
|
11084
|
-
TRIGGER: File operations (read, search, modify)
|
|
11085
|
-
ACTION: Use repository paths from \`.allagents/workspace.yaml\`, not assumptions
|
|
11086
|
-
<!-- WORKSPACE-RULES:END -->
|
|
11087
|
-
`;
|
|
11088
|
-
|
|
11089
|
-
// src/utils/workspace-parser.ts
|
|
11090
|
-
import { readFile } from "node:fs/promises";
|
|
11091
|
-
|
|
11092
11067
|
// node_modules/js-yaml/dist/js-yaml.mjs
|
|
11093
11068
|
/*! js-yaml 4.1.1 https://github.com/nodeca/js-yaml @license MIT */
|
|
11094
11069
|
function isNothing(subject) {
|
|
@@ -13736,6 +13711,32 @@ var safeLoad = renamed("safeLoad", "load");
|
|
|
13736
13711
|
var safeLoadAll = renamed("safeLoadAll", "loadAll");
|
|
13737
13712
|
var safeDump = renamed("safeDump", "dump");
|
|
13738
13713
|
|
|
13714
|
+
// src/core/sync.ts
|
|
13715
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
13716
|
+
import { rm, unlink, rmdir, copyFile } from "node:fs/promises";
|
|
13717
|
+
import { join as join7, resolve as resolve4, dirname as dirname4, relative } from "node:path";
|
|
13718
|
+
|
|
13719
|
+
// src/constants.ts
|
|
13720
|
+
var CONFIG_DIR = ".allagents";
|
|
13721
|
+
var SYNC_STATE_FILE = "sync-state.json";
|
|
13722
|
+
var WORKSPACE_CONFIG_FILE = "workspace.yaml";
|
|
13723
|
+
var WORKSPACE_CONFIG_PATH = `${CONFIG_DIR}/${WORKSPACE_CONFIG_FILE}`;
|
|
13724
|
+
var AGENT_FILES = ["AGENTS.md", "CLAUDE.md"];
|
|
13725
|
+
var WORKSPACE_RULES = `
|
|
13726
|
+
<!-- WORKSPACE-RULES:START -->
|
|
13727
|
+
## Rule: Workspace Discovery
|
|
13728
|
+
TRIGGER: Any task
|
|
13729
|
+
ACTION: Read \`.allagents/workspace.yaml\` to get repository paths and project domains
|
|
13730
|
+
|
|
13731
|
+
## Rule: Correct Repository Paths
|
|
13732
|
+
TRIGGER: File operations (read, search, modify)
|
|
13733
|
+
ACTION: Use repository paths from \`.allagents/workspace.yaml\`, not assumptions
|
|
13734
|
+
<!-- WORKSPACE-RULES:END -->
|
|
13735
|
+
`;
|
|
13736
|
+
|
|
13737
|
+
// src/utils/workspace-parser.ts
|
|
13738
|
+
import { readFile } from "node:fs/promises";
|
|
13739
|
+
|
|
13739
13740
|
// node_modules/zod/v3/external.js
|
|
13740
13741
|
var exports_external = {};
|
|
13741
13742
|
__export(exports_external, {
|
|
@@ -17719,12 +17720,12 @@ var RepositorySchema = exports_external.object({
|
|
|
17719
17720
|
var WorkspaceFileSchema = exports_external.union([
|
|
17720
17721
|
exports_external.string(),
|
|
17721
17722
|
exports_external.object({
|
|
17722
|
-
source: exports_external.string(),
|
|
17723
|
+
source: exports_external.string().optional(),
|
|
17723
17724
|
dest: exports_external.string().optional()
|
|
17724
17725
|
})
|
|
17725
17726
|
]);
|
|
17726
17727
|
var WorkspaceSchema = exports_external.object({
|
|
17727
|
-
source: exports_external.string(),
|
|
17728
|
+
source: exports_external.string().optional(),
|
|
17728
17729
|
files: exports_external.array(WorkspaceFileSchema)
|
|
17729
17730
|
});
|
|
17730
17731
|
var PluginSourceSchema = exports_external.string();
|
|
@@ -19430,6 +19431,31 @@ function validatePluginSource(source) {
|
|
|
19430
19431
|
}
|
|
19431
19432
|
return { valid: true };
|
|
19432
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
|
+
}
|
|
19433
19459
|
async function verifyGitHubUrlExists(source) {
|
|
19434
19460
|
const parsed = parseGitHubUrl(source);
|
|
19435
19461
|
if (!parsed) {
|
|
@@ -19751,6 +19777,7 @@ async function validateSkill(skillDir) {
|
|
|
19751
19777
|
}
|
|
19752
19778
|
|
|
19753
19779
|
// src/core/transform.ts
|
|
19780
|
+
var AGENT_FILES2 = ["AGENTS.md", "CLAUDE.md"];
|
|
19754
19781
|
async function ensureWorkspaceRules(filePath, rules) {
|
|
19755
19782
|
const rulesContent = rules ?? WORKSPACE_RULES;
|
|
19756
19783
|
const startMarker = "<!-- WORKSPACE-RULES:START -->";
|
|
@@ -19941,8 +19968,56 @@ async function copyPluginToWorkspace(pluginPath, workspacePath, client, options2
|
|
|
19941
19968
|
]);
|
|
19942
19969
|
return [...commandResults, ...skillResults, ...hookResults, ...agentResults];
|
|
19943
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
|
+
}
|
|
19944
20019
|
async function copyWorkspaceFiles(sourcePath, workspacePath, files, options2 = {}) {
|
|
19945
|
-
const { dryRun = false } = options2;
|
|
20020
|
+
const { dryRun = false, githubCache } = options2;
|
|
19946
20021
|
const results = [];
|
|
19947
20022
|
const stringPatterns = [];
|
|
19948
20023
|
const objectEntries = [];
|
|
@@ -19951,55 +20026,113 @@ async function copyWorkspaceFiles(sourcePath, workspacePath, files, options2 = {
|
|
|
19951
20026
|
if (typeof file === "string") {
|
|
19952
20027
|
stringPatterns.push(file);
|
|
19953
20028
|
} else {
|
|
19954
|
-
|
|
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 });
|
|
19955
20044
|
}
|
|
19956
20045
|
}
|
|
19957
20046
|
if (stringPatterns.length > 0) {
|
|
19958
|
-
|
|
19959
|
-
|
|
19960
|
-
|
|
19961
|
-
|
|
19962
|
-
|
|
19963
|
-
|
|
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) {
|
|
19964
20090
|
results.push({
|
|
19965
20091
|
source: resolved.sourcePath,
|
|
19966
20092
|
destination: destPath,
|
|
19967
20093
|
action: "failed",
|
|
19968
|
-
error:
|
|
20094
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
19969
20095
|
});
|
|
19970
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
|
+
});
|
|
19971
20112
|
continue;
|
|
19972
20113
|
}
|
|
19973
|
-
if (
|
|
19974
|
-
results.push({
|
|
19975
|
-
|
|
19976
|
-
|
|
19977
|
-
|
|
20114
|
+
if (resolved.error) {
|
|
20115
|
+
results.push({
|
|
20116
|
+
source: entry.source,
|
|
20117
|
+
destination: destPath,
|
|
20118
|
+
action: "failed",
|
|
20119
|
+
error: resolved.error
|
|
20120
|
+
});
|
|
19978
20121
|
continue;
|
|
19979
20122
|
}
|
|
19980
|
-
|
|
19981
|
-
|
|
19982
|
-
|
|
19983
|
-
await writeFile(destPath, content, "utf-8");
|
|
19984
|
-
results.push({ source: resolved.sourcePath, destination: destPath, action: "copied" });
|
|
19985
|
-
if (resolved.relativePath === "CLAUDE.md" || resolved.relativePath === "AGENTS.md") {
|
|
19986
|
-
copiedAgentFiles.push(resolved.relativePath);
|
|
19987
|
-
}
|
|
19988
|
-
} catch (error) {
|
|
20123
|
+
srcPath = resolved.path;
|
|
20124
|
+
} else {
|
|
20125
|
+
if (!sourcePath) {
|
|
19989
20126
|
results.push({
|
|
19990
|
-
source:
|
|
20127
|
+
source: entry.dest,
|
|
19991
20128
|
destination: destPath,
|
|
19992
20129
|
action: "failed",
|
|
19993
|
-
error:
|
|
20130
|
+
error: `Cannot resolve file '${entry.dest}' - no workspace.source configured and no explicit source provided`
|
|
19994
20131
|
});
|
|
20132
|
+
continue;
|
|
19995
20133
|
}
|
|
20134
|
+
srcPath = join4(sourcePath, entry.dest);
|
|
19996
20135
|
}
|
|
19997
|
-
}
|
|
19998
|
-
for (const entry of objectEntries) {
|
|
19999
|
-
const srcPath = join4(sourcePath, entry.source);
|
|
20000
|
-
const basename = entry.source.split("/").pop() || entry.source;
|
|
20001
|
-
const destFilename = entry.dest ?? basename;
|
|
20002
|
-
const destPath = join4(workspacePath, destFilename);
|
|
20003
20136
|
if (!existsSync3(srcPath)) {
|
|
20004
20137
|
results.push({
|
|
20005
20138
|
source: srcPath,
|
|
@@ -20011,8 +20144,8 @@ async function copyWorkspaceFiles(sourcePath, workspacePath, files, options2 = {
|
|
|
20011
20144
|
}
|
|
20012
20145
|
if (dryRun) {
|
|
20013
20146
|
results.push({ source: srcPath, destination: destPath, action: "copied" });
|
|
20014
|
-
if (
|
|
20015
|
-
copiedAgentFiles.push(
|
|
20147
|
+
if (AGENT_FILES2.includes(entry.dest)) {
|
|
20148
|
+
copiedAgentFiles.push(entry.dest);
|
|
20016
20149
|
}
|
|
20017
20150
|
continue;
|
|
20018
20151
|
}
|
|
@@ -20021,8 +20154,8 @@ async function copyWorkspaceFiles(sourcePath, workspacePath, files, options2 = {
|
|
|
20021
20154
|
const content = await readFile3(srcPath, "utf-8");
|
|
20022
20155
|
await writeFile(destPath, content, "utf-8");
|
|
20023
20156
|
results.push({ source: srcPath, destination: destPath, action: "copied" });
|
|
20024
|
-
if (
|
|
20025
|
-
copiedAgentFiles.push(
|
|
20157
|
+
if (AGENT_FILES2.includes(entry.dest)) {
|
|
20158
|
+
copiedAgentFiles.push(entry.dest);
|
|
20026
20159
|
}
|
|
20027
20160
|
} catch (error) {
|
|
20028
20161
|
results.push({
|
|
@@ -20033,19 +20166,19 @@ async function copyWorkspaceFiles(sourcePath, workspacePath, files, options2 = {
|
|
|
20033
20166
|
});
|
|
20034
20167
|
}
|
|
20035
20168
|
}
|
|
20036
|
-
|
|
20037
|
-
|
|
20038
|
-
|
|
20039
|
-
|
|
20040
|
-
|
|
20041
|
-
|
|
20042
|
-
|
|
20043
|
-
|
|
20044
|
-
|
|
20045
|
-
|
|
20046
|
-
|
|
20047
|
-
|
|
20048
|
-
}
|
|
20169
|
+
if (!dryRun) {
|
|
20170
|
+
for (const agentFile of copiedAgentFiles) {
|
|
20171
|
+
const targetPath = join4(workspacePath, agentFile);
|
|
20172
|
+
try {
|
|
20173
|
+
await injectWorkspaceRules(targetPath);
|
|
20174
|
+
} catch (error) {
|
|
20175
|
+
results.push({
|
|
20176
|
+
source: "WORKSPACE-RULES",
|
|
20177
|
+
destination: targetPath,
|
|
20178
|
+
action: "failed",
|
|
20179
|
+
error: error instanceof Error ? error.message : "Failed to inject WORKSPACE-RULES"
|
|
20180
|
+
});
|
|
20181
|
+
}
|
|
20049
20182
|
}
|
|
20050
20183
|
}
|
|
20051
20184
|
return results;
|
|
@@ -20449,6 +20582,130 @@ async function cleanupEmptyParents(workspacePath, filePath) {
|
|
|
20449
20582
|
}
|
|
20450
20583
|
}
|
|
20451
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
|
+
}
|
|
20452
20709
|
function collectSyncedPaths(copyResults, workspacePath, clients) {
|
|
20453
20710
|
const result = {};
|
|
20454
20711
|
for (const client of clients) {
|
|
@@ -20544,7 +20801,7 @@ async function copyValidatedPlugin(validatedPlugin, workspacePath, clients, dryR
|
|
|
20544
20801
|
};
|
|
20545
20802
|
}
|
|
20546
20803
|
async function syncWorkspace(workspacePath = process.cwd(), options2 = {}) {
|
|
20547
|
-
const { force = false, dryRun = false } = options2;
|
|
20804
|
+
const { force = false, dryRun = false, workspaceSourceBase } = options2;
|
|
20548
20805
|
const configDir = join7(workspacePath, CONFIG_DIR);
|
|
20549
20806
|
const configPath = join7(configDir, WORKSPACE_CONFIG_FILE);
|
|
20550
20807
|
if (!existsSync6(configPath)) {
|
|
@@ -20576,7 +20833,8 @@ async function syncWorkspace(workspacePath = process.cwd(), options2 = {}) {
|
|
|
20576
20833
|
const validatedPlugins = await validateAllPlugins(config.plugins, workspacePath, force);
|
|
20577
20834
|
let validatedWorkspaceSource = null;
|
|
20578
20835
|
if (config.workspace?.source) {
|
|
20579
|
-
|
|
20836
|
+
const sourceBasePath = workspaceSourceBase ?? workspacePath;
|
|
20837
|
+
validatedWorkspaceSource = await validatePlugin(config.workspace.source, sourceBasePath, force);
|
|
20580
20838
|
if (!validatedWorkspaceSource.success) {
|
|
20581
20839
|
return {
|
|
20582
20840
|
success: false,
|
|
@@ -20620,8 +20878,59 @@ ${errors2}`
|
|
|
20620
20878
|
}
|
|
20621
20879
|
const pluginResults = await Promise.all(validatedPlugins.map((validatedPlugin) => copyValidatedPlugin(validatedPlugin, workspacePath, config.clients, dryRun)));
|
|
20622
20880
|
let workspaceFileResults = [];
|
|
20623
|
-
if (config.workspace
|
|
20624
|
-
|
|
20881
|
+
if (config.workspace) {
|
|
20882
|
+
const sourcePath = validatedWorkspaceSource?.resolved;
|
|
20883
|
+
const filesToCopy = [...config.workspace.files];
|
|
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
|
+
};
|
|
20908
|
+
}
|
|
20909
|
+
githubCache = cache;
|
|
20910
|
+
}
|
|
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) {
|
|
20927
|
+
const claudePath = join7(workspacePath, "CLAUDE.md");
|
|
20928
|
+
const agentsPath = join7(workspacePath, "AGENTS.md");
|
|
20929
|
+
const claudeExistsInSource = existsSync6(join7(sourcePath, "CLAUDE.md"));
|
|
20930
|
+
if (!claudeExistsInSource && existsSync6(agentsPath) && !existsSync6(claudePath)) {
|
|
20931
|
+
await copyFile(agentsPath, claudePath);
|
|
20932
|
+
}
|
|
20933
|
+
}
|
|
20625
20934
|
}
|
|
20626
20935
|
let totalCopied = 0;
|
|
20627
20936
|
let totalFailed = 0;
|
|
@@ -20768,6 +21077,7 @@ async function initWorkspace(targetPath = ".", options2 = {}) {
|
|
|
20768
21077
|
await mkdir5(absoluteTarget, { recursive: true });
|
|
20769
21078
|
await mkdir5(configDir, { recursive: true });
|
|
20770
21079
|
let workspaceYamlContent;
|
|
21080
|
+
let sourceDir;
|
|
20771
21081
|
if (options2.from) {
|
|
20772
21082
|
const fromPath = resolve5(options2.from);
|
|
20773
21083
|
if (!existsSync7(fromPath)) {
|
|
@@ -20781,16 +21091,35 @@ async function initWorkspace(targetPath = ".", options2 = {}) {
|
|
|
20781
21091
|
const rootPath = join8(fromPath, WORKSPACE_CONFIG_FILE);
|
|
20782
21092
|
if (existsSync7(nestedPath)) {
|
|
20783
21093
|
sourceYamlPath = nestedPath;
|
|
21094
|
+
sourceDir = fromPath;
|
|
20784
21095
|
} else if (existsSync7(rootPath)) {
|
|
20785
21096
|
sourceYamlPath = rootPath;
|
|
21097
|
+
sourceDir = fromPath;
|
|
20786
21098
|
} else {
|
|
20787
21099
|
throw new Error(`No workspace.yaml found in: ${fromPath}
|
|
20788
21100
|
Expected at: ${nestedPath} or ${rootPath}`);
|
|
20789
21101
|
}
|
|
20790
21102
|
} else {
|
|
20791
21103
|
sourceYamlPath = fromPath;
|
|
21104
|
+
const parentDir = dirname5(fromPath);
|
|
21105
|
+
if (parentDir.endsWith(CONFIG_DIR)) {
|
|
21106
|
+
sourceDir = dirname5(parentDir);
|
|
21107
|
+
} else {
|
|
21108
|
+
sourceDir = parentDir;
|
|
21109
|
+
}
|
|
20792
21110
|
}
|
|
20793
21111
|
workspaceYamlContent = await readFile6(sourceYamlPath, "utf-8");
|
|
21112
|
+
if (sourceDir) {
|
|
21113
|
+
const parsed2 = load(workspaceYamlContent);
|
|
21114
|
+
const workspace = parsed2?.workspace;
|
|
21115
|
+
if (workspace?.source) {
|
|
21116
|
+
const source = workspace.source;
|
|
21117
|
+
if (!isGitHubUrl(source) && !isAbsolute2(source)) {
|
|
21118
|
+
workspace.source = resolve5(sourceDir, source);
|
|
21119
|
+
workspaceYamlContent = dump(parsed2, { lineWidth: -1 });
|
|
21120
|
+
}
|
|
21121
|
+
}
|
|
21122
|
+
}
|
|
20794
21123
|
console.log(`✓ Using workspace.yaml from: ${sourceYamlPath}`);
|
|
20795
21124
|
} else {
|
|
20796
21125
|
const defaultYamlPath = join8(defaultTemplatePath, CONFIG_DIR, WORKSPACE_CONFIG_FILE);
|
|
@@ -20800,12 +21129,37 @@ async function initWorkspace(targetPath = ".", options2 = {}) {
|
|
|
20800
21129
|
workspaceYamlContent = await readFile6(defaultYamlPath, "utf-8");
|
|
20801
21130
|
}
|
|
20802
21131
|
await writeFile4(configPath, workspaceYamlContent, "utf-8");
|
|
20803
|
-
const
|
|
20804
|
-
|
|
21132
|
+
const copiedAgentFiles = [];
|
|
21133
|
+
const effectiveSourceDir = sourceDir ?? defaultTemplatePath;
|
|
21134
|
+
for (const agentFile of AGENT_FILES) {
|
|
21135
|
+
const sourcePath = join8(effectiveSourceDir, agentFile);
|
|
21136
|
+
if (existsSync7(sourcePath)) {
|
|
21137
|
+
const content = await readFile6(sourcePath, "utf-8");
|
|
21138
|
+
await writeFile4(join8(absoluteTarget, agentFile), content, "utf-8");
|
|
21139
|
+
copiedAgentFiles.push(agentFile);
|
|
21140
|
+
}
|
|
21141
|
+
}
|
|
21142
|
+
if (copiedAgentFiles.length === 0) {
|
|
21143
|
+
await ensureWorkspaceRules(join8(absoluteTarget, "AGENTS.md"));
|
|
21144
|
+
copiedAgentFiles.push("AGENTS.md");
|
|
21145
|
+
} else {
|
|
21146
|
+
for (const agentFile of copiedAgentFiles) {
|
|
21147
|
+
await ensureWorkspaceRules(join8(absoluteTarget, agentFile));
|
|
21148
|
+
}
|
|
21149
|
+
}
|
|
21150
|
+
const parsed = load(workspaceYamlContent);
|
|
21151
|
+
const clients = parsed?.clients ?? [];
|
|
21152
|
+
if (clients.includes("claude") && !copiedAgentFiles.includes("CLAUDE.md") && copiedAgentFiles.includes("AGENTS.md")) {
|
|
21153
|
+
const agentsPath = join8(absoluteTarget, "AGENTS.md");
|
|
21154
|
+
const claudePath = join8(absoluteTarget, "CLAUDE.md");
|
|
21155
|
+
await copyFile2(agentsPath, claudePath);
|
|
21156
|
+
}
|
|
20805
21157
|
console.log(`✓ Workspace created at: ${absoluteTarget}`);
|
|
20806
21158
|
console.log(`
|
|
20807
21159
|
Syncing plugins...`);
|
|
20808
|
-
const syncResult = await syncWorkspace(absoluteTarget
|
|
21160
|
+
const syncResult = await syncWorkspace(absoluteTarget, {
|
|
21161
|
+
...sourceDir && { workspaceSourceBase: sourceDir }
|
|
21162
|
+
});
|
|
20809
21163
|
if (!syncResult.success && syncResult.error) {
|
|
20810
21164
|
if (!syncResult.error.includes("Plugin validation failed")) {
|
|
20811
21165
|
console.log(` Note: ${syncResult.error}`);
|