opencode-gitloops 0.1.0 → 0.2.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 +38 -0
- package/dist/config.d.ts +25 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +87 -0
- package/dist/config.js.map +1 -0
- package/dist/eviction.d.ts +12 -0
- package/dist/eviction.d.ts.map +1 -0
- package/dist/eviction.js +148 -0
- package/dist/eviction.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -7
- package/dist/index.js.map +1 -1
- package/dist/repo-manager.d.ts +3 -2
- package/dist/repo-manager.d.ts.map +1 -1
- package/dist/repo-manager.js +16 -9
- package/dist/repo-manager.js.map +1 -1
- package/package.json +3 -2
- package/schema/config.schema.json +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Gitloops
|
|
2
|
+
|
|
3
|
+
A plugin for [OpenCode](https://opencode.ai) that lets you clone and explore any public GitHub repository locally. Ask questions about a repo's code, structure, and patterns — all without leaving your terminal.
|
|
4
|
+
|
|
5
|
+
Gitloops registers a read-only agent with three custom tools (`gitloops_clone`, `gitloops_refresh`, `gitloops_list`) and restricts itself to `read`, `grep`, `glob`, and `list` — no file modifications, no shell access.
|
|
6
|
+
|
|
7
|
+
## Configuration
|
|
8
|
+
|
|
9
|
+
Gitloops auto-creates a config file on first load at:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
~/.config/opencode/plugin/gitloops.json
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"$schema": "https://raw.githubusercontent.com/maharshi-me/gitloops/main/schema/config.schema.json",
|
|
18
|
+
"max_repos": 10,
|
|
19
|
+
"cache_loc": "~/.cache/gitloops/repos",
|
|
20
|
+
"eviction_strategy": "lru"
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
| Option | Type | Default | Description |
|
|
25
|
+
|---|---|---|---|
|
|
26
|
+
| `max_repos` | `integer` | `10` | Maximum number of cached repos. When exceeded, repos are evicted automatically. |
|
|
27
|
+
| `cache_loc` | `string` | `~/.cache/gitloops/repos` | Directory where cloned repos are stored (`<cache_loc>/<owner>/<repo>/`). Supports `~`. |
|
|
28
|
+
| `eviction_strategy` | `string` | `"lru"` | Strategy for removing repos when `max_repos` is exceeded. |
|
|
29
|
+
|
|
30
|
+
### Eviction strategies
|
|
31
|
+
|
|
32
|
+
| Strategy | Behavior |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `lru` | Remove the least recently used repo (oldest modification time) |
|
|
35
|
+
| `fifo` | Remove the oldest cloned repo (oldest creation time) |
|
|
36
|
+
| `largest` | Remove the largest repo by disk size |
|
|
37
|
+
|
|
38
|
+
The `$schema` field enables autocompletion and validation in editors that support JSON Schema.
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type EvictionStrategy = "lru" | "fifo" | "largest";
|
|
2
|
+
export interface GitloopsConfig {
|
|
3
|
+
max_repos: number;
|
|
4
|
+
cache_loc: string;
|
|
5
|
+
eviction_strategy: EvictionStrategy;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Load the config from disk and merge with defaults.
|
|
9
|
+
* Result is cached after the first call.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getConfig(): Promise<GitloopsConfig>;
|
|
12
|
+
/**
|
|
13
|
+
* Reset the cached config so the next getConfig() call re-reads from disk.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resetConfigCache(): void;
|
|
16
|
+
/**
|
|
17
|
+
* Create the config file with defaults if it doesn't already exist.
|
|
18
|
+
* Returns true if a new file was created, false if it already existed.
|
|
19
|
+
*/
|
|
20
|
+
export declare function ensureConfigFile(): Promise<boolean>;
|
|
21
|
+
/**
|
|
22
|
+
* Returns the config file path (for logging).
|
|
23
|
+
*/
|
|
24
|
+
export declare function getConfigPath(): string;
|
|
25
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,MAAM,GAAG,SAAS,CAAA;AAEzD,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,iBAAiB,EAAE,gBAAgB,CAAA;CACpC;AAiCD;;;GAGG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC,cAAc,CAAC,CA6BzD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC,CAyBzD;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as fs from "fs/promises";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as os from "os";
|
|
4
|
+
const SCHEMA_URL = "https://raw.githubusercontent.com/maharshi-me/gitloops/main/schema/config.schema.json";
|
|
5
|
+
const CONFIG_PATH = path.join(os.homedir(), ".config", "opencode", "plugin", "gitloops.json");
|
|
6
|
+
const DEFAULT_CACHE_LOC = path.join(os.homedir(), ".cache", "gitloops", "repos");
|
|
7
|
+
const DEFAULTS = {
|
|
8
|
+
max_repos: 10,
|
|
9
|
+
cache_loc: DEFAULT_CACHE_LOC,
|
|
10
|
+
eviction_strategy: "lru",
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Resolve ~ to the user's home directory.
|
|
14
|
+
*/
|
|
15
|
+
function resolvePath(p) {
|
|
16
|
+
if (p.startsWith("~")) {
|
|
17
|
+
return path.join(os.homedir(), p.slice(1));
|
|
18
|
+
}
|
|
19
|
+
return path.resolve(p);
|
|
20
|
+
}
|
|
21
|
+
let _cached = null;
|
|
22
|
+
/**
|
|
23
|
+
* Load the config from disk and merge with defaults.
|
|
24
|
+
* Result is cached after the first call.
|
|
25
|
+
*/
|
|
26
|
+
export async function getConfig() {
|
|
27
|
+
if (_cached)
|
|
28
|
+
return _cached;
|
|
29
|
+
let raw = {};
|
|
30
|
+
try {
|
|
31
|
+
const contents = await fs.readFile(CONFIG_PATH, "utf8");
|
|
32
|
+
raw = JSON.parse(contents);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// File doesn't exist or is invalid — use defaults
|
|
36
|
+
}
|
|
37
|
+
const merged = {
|
|
38
|
+
max_repos: typeof raw.max_repos === "number" && raw.max_repos >= 1
|
|
39
|
+
? raw.max_repos
|
|
40
|
+
: DEFAULTS.max_repos,
|
|
41
|
+
cache_loc: raw.cache_loc
|
|
42
|
+
? resolvePath(raw.cache_loc)
|
|
43
|
+
: DEFAULTS.cache_loc,
|
|
44
|
+
eviction_strategy: raw.eviction_strategy &&
|
|
45
|
+
["lru", "fifo", "largest"].includes(raw.eviction_strategy)
|
|
46
|
+
? raw.eviction_strategy
|
|
47
|
+
: DEFAULTS.eviction_strategy,
|
|
48
|
+
};
|
|
49
|
+
_cached = merged;
|
|
50
|
+
return merged;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Reset the cached config so the next getConfig() call re-reads from disk.
|
|
54
|
+
*/
|
|
55
|
+
export function resetConfigCache() {
|
|
56
|
+
_cached = null;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Create the config file with defaults if it doesn't already exist.
|
|
60
|
+
* Returns true if a new file was created, false if it already existed.
|
|
61
|
+
*/
|
|
62
|
+
export async function ensureConfigFile() {
|
|
63
|
+
try {
|
|
64
|
+
await fs.access(CONFIG_PATH);
|
|
65
|
+
return false; // already exists
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// File doesn't exist — create it
|
|
69
|
+
}
|
|
70
|
+
const configDir = path.dirname(CONFIG_PATH);
|
|
71
|
+
await fs.mkdir(configDir, { recursive: true });
|
|
72
|
+
const defaultContent = {
|
|
73
|
+
$schema: SCHEMA_URL,
|
|
74
|
+
max_repos: DEFAULTS.max_repos,
|
|
75
|
+
cache_loc: "~/.cache/gitloops/repos",
|
|
76
|
+
eviction_strategy: DEFAULTS.eviction_strategy,
|
|
77
|
+
};
|
|
78
|
+
await fs.writeFile(CONFIG_PATH, JSON.stringify(defaultContent, null, 2) + "\n", "utf8");
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Returns the config file path (for logging).
|
|
83
|
+
*/
|
|
84
|
+
export function getConfigPath() {
|
|
85
|
+
return CONFIG_PATH;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { GitloopsConfig } from "./config";
|
|
2
|
+
/**
|
|
3
|
+
* Evict repos if the total count exceeds max_repos.
|
|
4
|
+
* Removes repos according to the configured eviction strategy until
|
|
5
|
+
* the count is within the limit.
|
|
6
|
+
*
|
|
7
|
+
* @param config - The current gitloops config
|
|
8
|
+
* @param currentSlug - Optional slug to protect from eviction (e.g. the repo just cloned)
|
|
9
|
+
* @returns Array of evicted repo slugs
|
|
10
|
+
*/
|
|
11
|
+
export declare function evictIfNeeded(config: GitloopsConfig, currentSlug?: string): Promise<string[]>;
|
|
12
|
+
//# sourceMappingURL=eviction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eviction.d.ts","sourceRoot":"","sources":["../src/eviction.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAoB,MAAM,UAAU,CAAA;AAwIhE;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,cAAc,EACtB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,EAAE,CAAC,CAuBnB"}
|
package/dist/eviction.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import * as fs from "fs/promises";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
/**
|
|
4
|
+
* Recursively compute the total size (in bytes) of a directory.
|
|
5
|
+
*/
|
|
6
|
+
async function getDirSize(dirPath) {
|
|
7
|
+
let total = 0;
|
|
8
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
9
|
+
for (const entry of entries) {
|
|
10
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
11
|
+
if (entry.isDirectory()) {
|
|
12
|
+
total += await getDirSize(fullPath);
|
|
13
|
+
}
|
|
14
|
+
else if (entry.isFile()) {
|
|
15
|
+
const stat = await fs.stat(fullPath);
|
|
16
|
+
total += stat.size;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return total;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Gather stats for all cached repos under the cache location.
|
|
23
|
+
*/
|
|
24
|
+
async function gatherRepoStats(cacheLoc) {
|
|
25
|
+
const stats = [];
|
|
26
|
+
let owners;
|
|
27
|
+
try {
|
|
28
|
+
owners = await fs.readdir(cacheLoc);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return stats;
|
|
32
|
+
}
|
|
33
|
+
for (const owner of owners) {
|
|
34
|
+
const ownerPath = path.join(cacheLoc, owner);
|
|
35
|
+
let ownerStat;
|
|
36
|
+
try {
|
|
37
|
+
ownerStat = await fs.stat(ownerPath);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (!ownerStat.isDirectory())
|
|
43
|
+
continue;
|
|
44
|
+
let repoNames;
|
|
45
|
+
try {
|
|
46
|
+
repoNames = await fs.readdir(ownerPath);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
for (const repo of repoNames) {
|
|
52
|
+
const repoPath = path.join(ownerPath, repo);
|
|
53
|
+
try {
|
|
54
|
+
const gitDir = path.join(repoPath, ".git");
|
|
55
|
+
const gitStat = await fs.stat(gitDir);
|
|
56
|
+
if (!gitStat.isDirectory())
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
continue; // not a git repo
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const stat = await fs.stat(repoPath);
|
|
64
|
+
stats.push({
|
|
65
|
+
slug: `${owner}/${repo}`,
|
|
66
|
+
repoPath,
|
|
67
|
+
mtime: stat.mtime,
|
|
68
|
+
birthtime: stat.birthtime,
|
|
69
|
+
size: 0, // computed lazily for "largest" strategy
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return stats;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Remove a cached repo directory and clean up empty parent owner directories.
|
|
81
|
+
*/
|
|
82
|
+
async function removeRepo(repoPath, cacheLoc) {
|
|
83
|
+
await fs.rm(repoPath, { recursive: true, force: true });
|
|
84
|
+
// Clean up empty owner directory
|
|
85
|
+
const ownerDir = path.dirname(repoPath);
|
|
86
|
+
try {
|
|
87
|
+
const remaining = await fs.readdir(ownerDir);
|
|
88
|
+
if (remaining.length === 0) {
|
|
89
|
+
await fs.rmdir(ownerDir);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Ignore — owner dir may already be gone
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Sort repos by eviction priority (first element = first to evict).
|
|
98
|
+
*/
|
|
99
|
+
async function sortByStrategy(repos, strategy) {
|
|
100
|
+
const sorted = [...repos];
|
|
101
|
+
switch (strategy) {
|
|
102
|
+
case "lru":
|
|
103
|
+
// Least recently modified first
|
|
104
|
+
sorted.sort((a, b) => a.mtime.getTime() - b.mtime.getTime());
|
|
105
|
+
break;
|
|
106
|
+
case "fifo":
|
|
107
|
+
// Oldest creation time first
|
|
108
|
+
sorted.sort((a, b) => a.birthtime.getTime() - b.birthtime.getTime());
|
|
109
|
+
break;
|
|
110
|
+
case "largest":
|
|
111
|
+
// Compute sizes then sort largest first
|
|
112
|
+
for (const repo of sorted) {
|
|
113
|
+
repo.size = await getDirSize(repo.repoPath);
|
|
114
|
+
}
|
|
115
|
+
sorted.sort((a, b) => b.size - a.size);
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
return sorted;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Evict repos if the total count exceeds max_repos.
|
|
122
|
+
* Removes repos according to the configured eviction strategy until
|
|
123
|
+
* the count is within the limit.
|
|
124
|
+
*
|
|
125
|
+
* @param config - The current gitloops config
|
|
126
|
+
* @param currentSlug - Optional slug to protect from eviction (e.g. the repo just cloned)
|
|
127
|
+
* @returns Array of evicted repo slugs
|
|
128
|
+
*/
|
|
129
|
+
export async function evictIfNeeded(config, currentSlug) {
|
|
130
|
+
const allRepos = await gatherRepoStats(config.cache_loc);
|
|
131
|
+
if (allRepos.length <= config.max_repos) {
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
const evictCount = allRepos.length - config.max_repos;
|
|
135
|
+
const sorted = await sortByStrategy(allRepos, config.eviction_strategy);
|
|
136
|
+
// Filter out the current repo from eviction candidates
|
|
137
|
+
const candidates = currentSlug
|
|
138
|
+
? sorted.filter((r) => r.slug !== currentSlug)
|
|
139
|
+
: sorted;
|
|
140
|
+
const evicted = [];
|
|
141
|
+
for (let i = 0; i < evictCount && i < candidates.length; i++) {
|
|
142
|
+
const repo = candidates[i];
|
|
143
|
+
await removeRepo(repo.repoPath, config.cache_loc);
|
|
144
|
+
evicted.push(repo.slug);
|
|
145
|
+
}
|
|
146
|
+
return evicted;
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=eviction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eviction.js","sourceRoot":"","sources":["../src/eviction.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAkDjD,eAAO,MAAM,cAAc,EAAE,MAoG5B,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { gitloops_clone, gitloops_refresh, gitloops_list } from "./tools";
|
|
2
2
|
import { ensureRepo } from "./repo-manager";
|
|
3
|
+
import { ensureConfigFile, getConfig, getConfigPath } from "./config";
|
|
3
4
|
import * as fs from "fs/promises";
|
|
4
5
|
import * as path from "path";
|
|
5
6
|
import * as os from "os";
|
|
6
|
-
|
|
7
|
+
function buildAgentMD(cacheLoc) {
|
|
8
|
+
return `---
|
|
7
9
|
description: Explore GitHub repositories locally. Clone any public repo and answer questions about its code, structure, and patterns.
|
|
8
10
|
mode: all
|
|
9
|
-
color: "#
|
|
11
|
+
color: "#ed5f00"
|
|
10
12
|
temperature: 0.1
|
|
11
13
|
tools:
|
|
12
14
|
read: true
|
|
@@ -25,7 +27,7 @@ permission:
|
|
|
25
27
|
bash: deny
|
|
26
28
|
---
|
|
27
29
|
|
|
28
|
-
You are
|
|
30
|
+
You are Gitloops, a read-only agent for exploring public GitHub repositories locally.
|
|
29
31
|
|
|
30
32
|
When a user asks about a repository:
|
|
31
33
|
1. Parse the repo slug from their message (e.g. "facebook/react" or a full GitHub URL)
|
|
@@ -40,21 +42,47 @@ Important rules:
|
|
|
40
42
|
- All file paths passed to read/grep/glob/list must be absolute paths under the repo's localPath.
|
|
41
43
|
- When switching between repos in the same session, always call \`gitloops_clone\` again for the new repo.
|
|
42
44
|
|
|
43
|
-
Repos are cached at:
|
|
45
|
+
Repos are cached at: ${cacheLoc}/<owner>/<repo>/
|
|
44
46
|
`;
|
|
47
|
+
}
|
|
45
48
|
export const GitLoopsPlugin = async ({ client }) => {
|
|
46
49
|
return {
|
|
47
|
-
// Write agent definition
|
|
50
|
+
// Write agent definition and ensure config on server connect (idempotent)
|
|
48
51
|
"server.connected": async () => {
|
|
52
|
+
// Ensure the plugin config file exists (auto-create with defaults)
|
|
49
53
|
try {
|
|
54
|
+
const created = await ensureConfigFile();
|
|
55
|
+
if (created) {
|
|
56
|
+
await client.app.log({
|
|
57
|
+
body: {
|
|
58
|
+
service: "opencode-gitloops",
|
|
59
|
+
level: "info",
|
|
60
|
+
message: `Config created with defaults at ${getConfigPath()}`,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
await client.app.log({
|
|
67
|
+
body: {
|
|
68
|
+
service: "opencode-gitloops",
|
|
69
|
+
level: "warn",
|
|
70
|
+
message: `Failed to create config: ${err.message || err}`,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
// Write agent definition to global config
|
|
75
|
+
try {
|
|
76
|
+
const config = await getConfig();
|
|
77
|
+
const agentMD = buildAgentMD(config.cache_loc);
|
|
50
78
|
const agentsDir = path.join(os.homedir(), ".config", "opencode", "agents");
|
|
51
79
|
const agentPath = path.join(agentsDir, "gitloops.md");
|
|
52
80
|
await fs.mkdir(agentsDir, { recursive: true });
|
|
53
81
|
const existing = await fs
|
|
54
82
|
.readFile(agentPath, "utf8")
|
|
55
83
|
.catch(() => null);
|
|
56
|
-
if (existing !==
|
|
57
|
-
await fs.writeFile(agentPath,
|
|
84
|
+
if (existing !== agentMD) {
|
|
85
|
+
await fs.writeFile(agentPath, agentMD, "utf8");
|
|
58
86
|
await client.app.log({
|
|
59
87
|
body: {
|
|
60
88
|
service: "opencode-gitloops",
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACzE,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAC3C,OAAO,KAAK,EAAE,MAAM,aAAa,CAAA;AACjC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAA;AAC5B,OAAO,KAAK,EAAE,MAAM,IAAI,CAAA;AAExB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/repo-manager.d.ts
CHANGED
|
@@ -30,15 +30,16 @@ export declare function parseRepoSlug(input: string): ParsedRepo;
|
|
|
30
30
|
/**
|
|
31
31
|
* Get the local filesystem path where a repo is (or would be) cached.
|
|
32
32
|
*/
|
|
33
|
-
export declare function getLocalPath(slug: string): string
|
|
33
|
+
export declare function getLocalPath(slug: string): Promise<string>;
|
|
34
34
|
/**
|
|
35
35
|
* Clone a repo (or fetch latest if already cloned). Returns metadata about the repo.
|
|
36
36
|
*
|
|
37
37
|
* Respects GITLOOPS_FULL_CLONE env var — if truthy, clones without --depth=1.
|
|
38
|
+
* Enforces max_repos limit via the configured eviction strategy after cloning.
|
|
38
39
|
*/
|
|
39
40
|
export declare function ensureRepo(input: string): Promise<RepoInfo>;
|
|
40
41
|
/**
|
|
41
|
-
* List all repos currently cached under
|
|
42
|
+
* List all repos currently cached under the configured cache location.
|
|
42
43
|
*/
|
|
43
44
|
export declare function listCachedRepos(): Promise<CachedRepo[]>;
|
|
44
45
|
//# sourceMappingURL=repo-manager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repo-manager.d.ts","sourceRoot":"","sources":["../src/repo-manager.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"repo-manager.d.ts","sourceRoot":"","sources":["../src/repo-manager.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CA8CvD;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAIhE;AA0BD;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAwDjE;AAED;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAgD7D"}
|
package/dist/repo-manager.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { $ } from "bun";
|
|
2
2
|
import * as fs from "fs/promises";
|
|
3
3
|
import * as path from "path";
|
|
4
|
-
import
|
|
5
|
-
|
|
4
|
+
import { getConfig } from "./config";
|
|
5
|
+
import { evictIfNeeded } from "./eviction";
|
|
6
6
|
/**
|
|
7
7
|
* Parse a repo slug or URL into its components.
|
|
8
8
|
*
|
|
@@ -52,9 +52,10 @@ export function parseRepoSlug(input) {
|
|
|
52
52
|
/**
|
|
53
53
|
* Get the local filesystem path where a repo is (or would be) cached.
|
|
54
54
|
*/
|
|
55
|
-
export function getLocalPath(slug) {
|
|
55
|
+
export async function getLocalPath(slug) {
|
|
56
56
|
const { owner, repo } = parseRepoSlug(slug);
|
|
57
|
-
|
|
57
|
+
const config = await getConfig();
|
|
58
|
+
return path.join(config.cache_loc, owner, repo);
|
|
58
59
|
}
|
|
59
60
|
/**
|
|
60
61
|
* Check if a directory exists.
|
|
@@ -84,10 +85,12 @@ async function getLastCommit(repoPath) {
|
|
|
84
85
|
* Clone a repo (or fetch latest if already cloned). Returns metadata about the repo.
|
|
85
86
|
*
|
|
86
87
|
* Respects GITLOOPS_FULL_CLONE env var — if truthy, clones without --depth=1.
|
|
88
|
+
* Enforces max_repos limit via the configured eviction strategy after cloning.
|
|
87
89
|
*/
|
|
88
90
|
export async function ensureRepo(input) {
|
|
89
91
|
const parsed = parseRepoSlug(input);
|
|
90
|
-
const
|
|
92
|
+
const config = await getConfig();
|
|
93
|
+
const localPath = path.join(config.cache_loc, parsed.owner, parsed.repo);
|
|
91
94
|
const fullClone = process.env.GITLOOPS_FULL_CLONE === "true";
|
|
92
95
|
const depthArgs = fullClone ? [] : ["--depth=1"];
|
|
93
96
|
if (await dirExists(path.join(localPath, ".git"))) {
|
|
@@ -120,6 +123,8 @@ export async function ensureRepo(input) {
|
|
|
120
123
|
}
|
|
121
124
|
throw new Error(`Failed to clone ${parsed.slug}: ${err.message || err}`);
|
|
122
125
|
}
|
|
126
|
+
// Evict old repos if we've exceeded the max
|
|
127
|
+
await evictIfNeeded(config, parsed.slug);
|
|
123
128
|
}
|
|
124
129
|
const lastCommit = await getLastCommit(localPath);
|
|
125
130
|
return {
|
|
@@ -132,22 +137,24 @@ export async function ensureRepo(input) {
|
|
|
132
137
|
};
|
|
133
138
|
}
|
|
134
139
|
/**
|
|
135
|
-
* List all repos currently cached under
|
|
140
|
+
* List all repos currently cached under the configured cache location.
|
|
136
141
|
*/
|
|
137
142
|
export async function listCachedRepos() {
|
|
143
|
+
const config = await getConfig();
|
|
144
|
+
const cacheLoc = config.cache_loc;
|
|
138
145
|
const repos = [];
|
|
139
|
-
if (!(await dirExists(
|
|
146
|
+
if (!(await dirExists(cacheLoc))) {
|
|
140
147
|
return repos;
|
|
141
148
|
}
|
|
142
149
|
let owners;
|
|
143
150
|
try {
|
|
144
|
-
owners = await fs.readdir(
|
|
151
|
+
owners = await fs.readdir(cacheLoc);
|
|
145
152
|
}
|
|
146
153
|
catch {
|
|
147
154
|
return repos;
|
|
148
155
|
}
|
|
149
156
|
for (const owner of owners) {
|
|
150
|
-
const ownerPath = path.join(
|
|
157
|
+
const ownerPath = path.join(cacheLoc, owner);
|
|
151
158
|
if (!(await dirExists(ownerPath)))
|
|
152
159
|
continue;
|
|
153
160
|
let repoNames;
|
package/dist/repo-manager.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repo-manager.js","sourceRoot":"","sources":["../src/repo-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,MAAM,aAAa,CAAA;AACjC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAA;AAC5B,OAAO,
|
|
1
|
+
{"version":3,"file":"repo-manager.js","sourceRoot":"","sources":["../src/repo-manager.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-gitloops",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "GitHub repo explorer agent and plugin for OpenCode. Clone and explore any public GitHub repo locally.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
|
-
"dist/"
|
|
15
|
+
"dist/",
|
|
16
|
+
"schema/"
|
|
16
17
|
],
|
|
17
18
|
"keywords": [
|
|
18
19
|
"opencode",
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://raw.githubusercontent.com/maharshi-me/gitloops/main/schema/config.schema.json",
|
|
4
|
+
"title": "Gitloops Configuration",
|
|
5
|
+
"description": "Configuration for the opencode-gitloops plugin.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"$schema": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "Path or URL to the JSON schema for this config file."
|
|
11
|
+
},
|
|
12
|
+
"max_repos": {
|
|
13
|
+
"type": "integer",
|
|
14
|
+
"minimum": 1,
|
|
15
|
+
"default": 10,
|
|
16
|
+
"description": "Maximum number of cached repositories. When exceeded, repos are evicted based on the eviction_strategy."
|
|
17
|
+
},
|
|
18
|
+
"cache_loc": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"default": "~/.cache/gitloops/repos",
|
|
21
|
+
"description": "Directory where cloned repositories are stored. Repos are placed directly at <cache_loc>/<owner>/<repo>/. Supports ~ for the home directory."
|
|
22
|
+
},
|
|
23
|
+
"eviction_strategy": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"enum": ["lru", "fifo", "largest"],
|
|
26
|
+
"default": "lru",
|
|
27
|
+
"description": "Strategy used to evict repos when max_repos is exceeded. 'lru' removes least recently used, 'fifo' removes oldest cloned, 'largest' removes the largest repo by disk size."
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"additionalProperties": false
|
|
31
|
+
}
|