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.
Files changed (3) hide show
  1. package/README.md +53 -1
  2. package/dist/index.js +489 -90
  3. 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 readFileSync2 } from "node:fs";
11058
- import { dirname as dirname6, join as join11 } from "node:path";
11059
- import { fileURLToPath as fileURLToPath3 } from "node:url";
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
- 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 });
19957
20044
  }
19958
20045
  }
19959
20046
  if (stringPatterns.length > 0) {
19960
- const resolvedFiles = await resolveGlobPatterns(sourcePath, stringPatterns);
19961
- for (const resolved of resolvedFiles) {
19962
- const destPath = join4(workspacePath, resolved.relativePath);
19963
- if (!existsSync3(resolved.sourcePath)) {
19964
- const wasLiteral = stringPatterns.some((p) => !isGlobPattern(p) && !p.startsWith("!") && p === resolved.relativePath);
19965
- 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) {
19966
20090
  results.push({
19967
20091
  source: resolved.sourcePath,
19968
20092
  destination: destPath,
19969
20093
  action: "failed",
19970
- error: `Source file not found: ${resolved.sourcePath}`
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 (dryRun) {
19976
- results.push({ source: resolved.sourcePath, destination: destPath, action: "copied" });
19977
- if (AGENT_FILES2.includes(resolved.relativePath)) {
19978
- copiedAgentFiles.push(resolved.relativePath);
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
- try {
19983
- await mkdir2(dirname2(destPath), { recursive: true });
19984
- const content = await readFile3(resolved.sourcePath, "utf-8");
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: resolved.sourcePath,
20127
+ source: entry.dest,
19993
20128
  destination: destPath,
19994
20129
  action: "failed",
19995
- error: error instanceof Error ? error.message : "Unknown 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(destFilename)) {
20017
- copiedAgentFiles.push(destFilename);
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(destFilename)) {
20027
- copiedAgentFiles.push(destFilename);
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 && validatedWorkspaceSource) {
20627
- const sourcePath = validatedWorkspaceSource.resolved;
20881
+ if (config.workspace) {
20882
+ const sourcePath = validatedWorkspaceSource?.resolved;
20628
20883
  const filesToCopy = [...config.workspace.files];
20629
- for (const agentFile of AGENT_FILES) {
20630
- const agentPath = join7(sourcePath, agentFile);
20631
- if (existsSync6(agentPath) && !filesToCopy.includes(agentFile)) {
20632
- filesToCopy.push(agentFile);
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
- workspaceFileResults = await copyWorkspaceFiles(sourcePath, workspacePath, filesToCopy, { dryRun });
20636
- if (!dryRun && config.clients.includes("claude")) {
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
- const fromPath = resolve5(options2.from);
20793
- if (!existsSync7(fromPath)) {
20794
- throw new Error(`Template not found: ${fromPath}`);
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
- } else {
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
- workspace.source = resolve5(sourceDir, source);
20829
- workspaceYamlContent = dump(parsed2, { lineWidth: -1 });
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
- async function getCurrentVersion() {
21868
+ function getCurrentVersion() {
21470
21869
  try {
21471
- const { createRequire: createRequire2 } = await import("node:module");
21472
- const require2 = createRequire2(import.meta.url);
21473
- const pkg = require2("../../../package.json");
21474
- return pkg.version;
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 = await getCurrentVersion();
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 = dirname6(fileURLToPath3(import.meta.url));
21529
- var packageJsonPath = join11(__dirname2, "..", "package.json");
21530
- var packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
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.2.2",
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",