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.
Files changed (3) hide show
  1. package/README.md +32 -1
  2. package/dist/index.js +438 -84
  3. 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
- objectEntries.push({ source: file.source, dest: file.dest });
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
- const resolvedFiles = await resolveGlobPatterns(sourcePath, stringPatterns);
19959
- for (const resolved of resolvedFiles) {
19960
- const destPath = join4(workspacePath, resolved.relativePath);
19961
- if (!existsSync3(resolved.sourcePath)) {
19962
- const wasLiteral = stringPatterns.some((p) => !isGlobPattern(p) && !p.startsWith("!") && p === resolved.relativePath);
19963
- if (wasLiteral) {
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: `Source file not found: ${resolved.sourcePath}`
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 (dryRun) {
19974
- results.push({ source: resolved.sourcePath, destination: destPath, action: "copied" });
19975
- if (resolved.relativePath === "CLAUDE.md" || resolved.relativePath === "AGENTS.md") {
19976
- copiedAgentFiles.push(resolved.relativePath);
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
- try {
19981
- await mkdir2(dirname2(destPath), { recursive: true });
19982
- const content = await readFile3(resolved.sourcePath, "utf-8");
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: resolved.sourcePath,
20127
+ source: entry.dest,
19991
20128
  destination: destPath,
19992
20129
  action: "failed",
19993
- error: error instanceof Error ? error.message : "Unknown 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 (destFilename === "CLAUDE.md" || destFilename === "AGENTS.md") {
20015
- copiedAgentFiles.push(destFilename);
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 (destFilename === "CLAUDE.md" || destFilename === "AGENTS.md") {
20025
- copiedAgentFiles.push(destFilename);
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
- const firstAgentFile = copiedAgentFiles[0];
20037
- if (firstAgentFile && !dryRun) {
20038
- const targetFile = copiedAgentFiles.includes("AGENTS.md") ? "AGENTS.md" : firstAgentFile;
20039
- const targetPath = join4(workspacePath, targetFile);
20040
- try {
20041
- await injectWorkspaceRules(targetPath);
20042
- } catch (error) {
20043
- results.push({
20044
- source: "WORKSPACE-RULES",
20045
- destination: targetPath,
20046
- action: "failed",
20047
- error: error instanceof Error ? error.message : "Failed to inject WORKSPACE-RULES"
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
- validatedWorkspaceSource = await validatePlugin(config.workspace.source, workspacePath, force);
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 && validatedWorkspaceSource) {
20624
- workspaceFileResults = await copyWorkspaceFiles(validatedWorkspaceSource.resolved, workspacePath, config.workspace.files, { dryRun });
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 targetAgentsPath = join8(absoluteTarget, "AGENTS.md");
20804
- await ensureWorkspaceRules(targetAgentsPath);
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}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "allagents",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "CLI tool for managing multi-repo AI agent workspaces with plugin synchronization",
5
5
  "type": "module",
6
6
  "bin": {