opencode-gitloops 0.1.0 → 0.2.1
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 +104 -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 +170 -0
- package/dist/eviction.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -28
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +26 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +32 -0
- package/dist/logger.js.map +1 -0
- package/dist/repo-manager.d.ts +3 -2
- package/dist/repo-manager.d.ts.map +1 -1
- package/dist/repo-manager.js +47 -9
- package/dist/repo-manager.js.map +1 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +19 -0
- package/dist/tools.js.map +1 -1
- package/package.json +4 -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":"AAKA,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,CA8CzD;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,104 @@
|
|
|
1
|
+
import * as fs from "fs/promises";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as os from "os";
|
|
4
|
+
import { logger } from "./logger";
|
|
5
|
+
const SCHEMA_URL = "https://raw.githubusercontent.com/maharshi-me/gitloops/main/schema/config.schema.json";
|
|
6
|
+
const CONFIG_PATH = path.join(os.homedir(), ".config", "opencode", "plugin", "gitloops.json");
|
|
7
|
+
const DEFAULT_CACHE_LOC = path.join(os.homedir(), ".cache", "gitloops", "repos");
|
|
8
|
+
const DEFAULTS = {
|
|
9
|
+
max_repos: 10,
|
|
10
|
+
cache_loc: DEFAULT_CACHE_LOC,
|
|
11
|
+
eviction_strategy: "lru",
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Resolve ~ to the user's home directory.
|
|
15
|
+
*/
|
|
16
|
+
function resolvePath(p) {
|
|
17
|
+
if (p.startsWith("~")) {
|
|
18
|
+
return path.join(os.homedir(), p.slice(1));
|
|
19
|
+
}
|
|
20
|
+
return path.resolve(p);
|
|
21
|
+
}
|
|
22
|
+
let _cached = null;
|
|
23
|
+
/**
|
|
24
|
+
* Load the config from disk and merge with defaults.
|
|
25
|
+
* Result is cached after the first call.
|
|
26
|
+
*/
|
|
27
|
+
export async function getConfig() {
|
|
28
|
+
if (_cached)
|
|
29
|
+
return _cached;
|
|
30
|
+
let raw = {};
|
|
31
|
+
try {
|
|
32
|
+
const contents = await fs.readFile(CONFIG_PATH, "utf8");
|
|
33
|
+
raw = JSON.parse(contents);
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
// Distinguish between missing file and invalid JSON
|
|
37
|
+
if (err?.code === "ENOENT") {
|
|
38
|
+
await logger.debug("Config file not found, using defaults", {
|
|
39
|
+
path: CONFIG_PATH,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
await logger.warn("Config file has invalid JSON, falling back to defaults", {
|
|
44
|
+
path: CONFIG_PATH,
|
|
45
|
+
error: err?.message || String(err),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const merged = {
|
|
50
|
+
max_repos: typeof raw.max_repos === "number" && raw.max_repos >= 1
|
|
51
|
+
? raw.max_repos
|
|
52
|
+
: DEFAULTS.max_repos,
|
|
53
|
+
cache_loc: raw.cache_loc
|
|
54
|
+
? resolvePath(raw.cache_loc)
|
|
55
|
+
: DEFAULTS.cache_loc,
|
|
56
|
+
eviction_strategy: raw.eviction_strategy &&
|
|
57
|
+
["lru", "fifo", "largest"].includes(raw.eviction_strategy)
|
|
58
|
+
? raw.eviction_strategy
|
|
59
|
+
: DEFAULTS.eviction_strategy,
|
|
60
|
+
};
|
|
61
|
+
_cached = merged;
|
|
62
|
+
await logger.info("Config loaded", {
|
|
63
|
+
max_repos: merged.max_repos,
|
|
64
|
+
cache_loc: merged.cache_loc,
|
|
65
|
+
eviction_strategy: merged.eviction_strategy,
|
|
66
|
+
});
|
|
67
|
+
return merged;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Reset the cached config so the next getConfig() call re-reads from disk.
|
|
71
|
+
*/
|
|
72
|
+
export function resetConfigCache() {
|
|
73
|
+
_cached = null;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Create the config file with defaults if it doesn't already exist.
|
|
77
|
+
* Returns true if a new file was created, false if it already existed.
|
|
78
|
+
*/
|
|
79
|
+
export async function ensureConfigFile() {
|
|
80
|
+
try {
|
|
81
|
+
await fs.access(CONFIG_PATH);
|
|
82
|
+
return false; // already exists
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// File doesn't exist — create it
|
|
86
|
+
}
|
|
87
|
+
const configDir = path.dirname(CONFIG_PATH);
|
|
88
|
+
await fs.mkdir(configDir, { recursive: true });
|
|
89
|
+
const defaultContent = {
|
|
90
|
+
$schema: SCHEMA_URL,
|
|
91
|
+
max_repos: DEFAULTS.max_repos,
|
|
92
|
+
cache_loc: "~/.cache/gitloops/repos",
|
|
93
|
+
eviction_strategy: DEFAULTS.eviction_strategy,
|
|
94
|
+
};
|
|
95
|
+
await fs.writeFile(CONFIG_PATH, JSON.stringify(defaultContent, null, 2) + "\n", "utf8");
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Returns the config file path (for logging).
|
|
100
|
+
*/
|
|
101
|
+
export function getConfigPath() {
|
|
102
|
+
return CONFIG_PATH;
|
|
103
|
+
}
|
|
104
|
+
//# 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;AAyIhE;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,cAAc,EACtB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,EAAE,CAAC,CAiDnB"}
|
package/dist/eviction.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import * as fs from "fs/promises";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { logger } from "./logger";
|
|
4
|
+
/**
|
|
5
|
+
* Recursively compute the total size (in bytes) of a directory.
|
|
6
|
+
*/
|
|
7
|
+
async function getDirSize(dirPath) {
|
|
8
|
+
let total = 0;
|
|
9
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
10
|
+
for (const entry of entries) {
|
|
11
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
12
|
+
if (entry.isDirectory()) {
|
|
13
|
+
total += await getDirSize(fullPath);
|
|
14
|
+
}
|
|
15
|
+
else if (entry.isFile()) {
|
|
16
|
+
const stat = await fs.stat(fullPath);
|
|
17
|
+
total += stat.size;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return total;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Gather stats for all cached repos under the cache location.
|
|
24
|
+
*/
|
|
25
|
+
async function gatherRepoStats(cacheLoc) {
|
|
26
|
+
const stats = [];
|
|
27
|
+
let owners;
|
|
28
|
+
try {
|
|
29
|
+
owners = await fs.readdir(cacheLoc);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return stats;
|
|
33
|
+
}
|
|
34
|
+
for (const owner of owners) {
|
|
35
|
+
const ownerPath = path.join(cacheLoc, owner);
|
|
36
|
+
let ownerStat;
|
|
37
|
+
try {
|
|
38
|
+
ownerStat = await fs.stat(ownerPath);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (!ownerStat.isDirectory())
|
|
44
|
+
continue;
|
|
45
|
+
let repoNames;
|
|
46
|
+
try {
|
|
47
|
+
repoNames = await fs.readdir(ownerPath);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
for (const repo of repoNames) {
|
|
53
|
+
const repoPath = path.join(ownerPath, repo);
|
|
54
|
+
try {
|
|
55
|
+
const gitDir = path.join(repoPath, ".git");
|
|
56
|
+
const gitStat = await fs.stat(gitDir);
|
|
57
|
+
if (!gitStat.isDirectory())
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
continue; // not a git repo
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const stat = await fs.stat(repoPath);
|
|
65
|
+
stats.push({
|
|
66
|
+
slug: `${owner}/${repo}`,
|
|
67
|
+
repoPath,
|
|
68
|
+
mtime: stat.mtime,
|
|
69
|
+
birthtime: stat.birthtime,
|
|
70
|
+
size: 0, // computed lazily for "largest" strategy
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return stats;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Remove a cached repo directory and clean up empty parent owner directories.
|
|
82
|
+
*/
|
|
83
|
+
async function removeRepo(repoPath, cacheLoc) {
|
|
84
|
+
await fs.rm(repoPath, { recursive: true, force: true });
|
|
85
|
+
// Clean up empty owner directory
|
|
86
|
+
const ownerDir = path.dirname(repoPath);
|
|
87
|
+
try {
|
|
88
|
+
const remaining = await fs.readdir(ownerDir);
|
|
89
|
+
if (remaining.length === 0) {
|
|
90
|
+
await fs.rmdir(ownerDir);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Ignore — owner dir may already be gone
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Sort repos by eviction priority (first element = first to evict).
|
|
99
|
+
*/
|
|
100
|
+
async function sortByStrategy(repos, strategy) {
|
|
101
|
+
const sorted = [...repos];
|
|
102
|
+
switch (strategy) {
|
|
103
|
+
case "lru":
|
|
104
|
+
// Least recently modified first
|
|
105
|
+
sorted.sort((a, b) => a.mtime.getTime() - b.mtime.getTime());
|
|
106
|
+
break;
|
|
107
|
+
case "fifo":
|
|
108
|
+
// Oldest creation time first
|
|
109
|
+
sorted.sort((a, b) => a.birthtime.getTime() - b.birthtime.getTime());
|
|
110
|
+
break;
|
|
111
|
+
case "largest":
|
|
112
|
+
// Compute sizes then sort largest first
|
|
113
|
+
for (const repo of sorted) {
|
|
114
|
+
repo.size = await getDirSize(repo.repoPath);
|
|
115
|
+
}
|
|
116
|
+
sorted.sort((a, b) => b.size - a.size);
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
return sorted;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Evict repos if the total count exceeds max_repos.
|
|
123
|
+
* Removes repos according to the configured eviction strategy until
|
|
124
|
+
* the count is within the limit.
|
|
125
|
+
*
|
|
126
|
+
* @param config - The current gitloops config
|
|
127
|
+
* @param currentSlug - Optional slug to protect from eviction (e.g. the repo just cloned)
|
|
128
|
+
* @returns Array of evicted repo slugs
|
|
129
|
+
*/
|
|
130
|
+
export async function evictIfNeeded(config, currentSlug) {
|
|
131
|
+
const allRepos = await gatherRepoStats(config.cache_loc);
|
|
132
|
+
if (allRepos.length <= config.max_repos) {
|
|
133
|
+
await logger.debug("Eviction check passed", {
|
|
134
|
+
cached: allRepos.length,
|
|
135
|
+
max: config.max_repos,
|
|
136
|
+
});
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
const evictCount = allRepos.length - config.max_repos;
|
|
140
|
+
await logger.info(`Eviction triggered: ${allRepos.length} cached repos exceeds limit of ${config.max_repos}`, { strategy: config.eviction_strategy, evictCount });
|
|
141
|
+
const sorted = await sortByStrategy(allRepos, config.eviction_strategy);
|
|
142
|
+
// Filter out the current repo from eviction candidates
|
|
143
|
+
const candidates = currentSlug
|
|
144
|
+
? sorted.filter((r) => r.slug !== currentSlug)
|
|
145
|
+
: sorted;
|
|
146
|
+
const evicted = [];
|
|
147
|
+
for (let i = 0; i < evictCount && i < candidates.length; i++) {
|
|
148
|
+
const repo = candidates[i];
|
|
149
|
+
try {
|
|
150
|
+
await removeRepo(repo.repoPath, config.cache_loc);
|
|
151
|
+
evicted.push(repo.slug);
|
|
152
|
+
await logger.info(`Evicted repo: ${repo.slug}`, {
|
|
153
|
+
strategy: config.eviction_strategy,
|
|
154
|
+
path: repo.repoPath,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
await logger.warn(`Failed to evict repo: ${repo.slug}`, {
|
|
159
|
+
path: repo.repoPath,
|
|
160
|
+
error: err?.message || String(err),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
await logger.info(`Eviction complete: removed ${evicted.length} repo(s)`, {
|
|
165
|
+
evicted,
|
|
166
|
+
remaining: allRepos.length - evicted.length,
|
|
167
|
+
});
|
|
168
|
+
return evicted;
|
|
169
|
+
}
|
|
170
|
+
//# 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;AAmDjD,eAAO,MAAM,cAAc,EAAE,MA4E5B,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
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";
|
|
4
|
+
import { initLogger, logger } from "./logger";
|
|
3
5
|
import * as fs from "fs/promises";
|
|
4
6
|
import * as path from "path";
|
|
5
7
|
import * as os from "os";
|
|
6
|
-
|
|
8
|
+
function buildAgentMD(cacheLoc) {
|
|
9
|
+
return `---
|
|
7
10
|
description: Explore GitHub repositories locally. Clone any public repo and answer questions about its code, structure, and patterns.
|
|
8
11
|
mode: all
|
|
9
|
-
color: "#
|
|
12
|
+
color: "#ed5f00"
|
|
10
13
|
temperature: 0.1
|
|
11
14
|
tools:
|
|
12
15
|
read: true
|
|
@@ -25,7 +28,7 @@ permission:
|
|
|
25
28
|
bash: deny
|
|
26
29
|
---
|
|
27
30
|
|
|
28
|
-
You are
|
|
31
|
+
You are Gitloops, a read-only agent for exploring public GitHub repositories locally.
|
|
29
32
|
|
|
30
33
|
When a user asks about a repository:
|
|
31
34
|
1. Parse the repo slug from their message (e.g. "facebook/react" or a full GitHub URL)
|
|
@@ -40,38 +43,43 @@ Important rules:
|
|
|
40
43
|
- All file paths passed to read/grep/glob/list must be absolute paths under the repo's localPath.
|
|
41
44
|
- When switching between repos in the same session, always call \`gitloops_clone\` again for the new repo.
|
|
42
45
|
|
|
43
|
-
Repos are cached at:
|
|
46
|
+
Repos are cached at: ${cacheLoc}/<owner>/<repo>/
|
|
44
47
|
`;
|
|
48
|
+
}
|
|
45
49
|
export const GitLoopsPlugin = async ({ client }) => {
|
|
50
|
+
// Initialize the logger so all modules can use it
|
|
51
|
+
initLogger(client);
|
|
52
|
+
await logger.info("Gitloops plugin initialized");
|
|
46
53
|
return {
|
|
47
|
-
// Write agent definition
|
|
54
|
+
// Write agent definition and ensure config on server connect (idempotent)
|
|
48
55
|
"server.connected": async () => {
|
|
56
|
+
// Ensure the plugin config file exists (auto-create with defaults)
|
|
49
57
|
try {
|
|
58
|
+
const created = await ensureConfigFile();
|
|
59
|
+
if (created) {
|
|
60
|
+
await logger.info(`Config created with defaults at ${getConfigPath()}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
await logger.warn(`Failed to create config: ${err.message || err}`);
|
|
65
|
+
}
|
|
66
|
+
// Write agent definition to global config
|
|
67
|
+
try {
|
|
68
|
+
const config = await getConfig();
|
|
69
|
+
const agentMD = buildAgentMD(config.cache_loc);
|
|
50
70
|
const agentsDir = path.join(os.homedir(), ".config", "opencode", "agents");
|
|
51
71
|
const agentPath = path.join(agentsDir, "gitloops.md");
|
|
52
72
|
await fs.mkdir(agentsDir, { recursive: true });
|
|
53
73
|
const existing = await fs
|
|
54
74
|
.readFile(agentPath, "utf8")
|
|
55
75
|
.catch(() => null);
|
|
56
|
-
if (existing !==
|
|
57
|
-
await fs.writeFile(agentPath,
|
|
58
|
-
await
|
|
59
|
-
body: {
|
|
60
|
-
service: "opencode-gitloops",
|
|
61
|
-
level: "info",
|
|
62
|
-
message: `Agent definition written to ${agentPath}`,
|
|
63
|
-
},
|
|
64
|
-
});
|
|
76
|
+
if (existing !== agentMD) {
|
|
77
|
+
await fs.writeFile(agentPath, agentMD, "utf8");
|
|
78
|
+
await logger.info(`Agent definition written to ${agentPath}`);
|
|
65
79
|
}
|
|
66
80
|
}
|
|
67
81
|
catch (err) {
|
|
68
|
-
await
|
|
69
|
-
body: {
|
|
70
|
-
service: "opencode-gitloops",
|
|
71
|
-
level: "warn",
|
|
72
|
-
message: `Failed to write agent definition: ${err.message || err}`,
|
|
73
|
-
},
|
|
74
|
-
});
|
|
82
|
+
await logger.warn(`Failed to write agent definition: ${err.message || err}`);
|
|
75
83
|
}
|
|
76
84
|
},
|
|
77
85
|
// Auto-fetch: when a gitloops session is created, pre-warm clone if a slug
|
|
@@ -87,13 +95,7 @@ export const GitLoopsPlugin = async ({ client }) => {
|
|
|
87
95
|
const slugMatch = session.title?.match(/([a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+)/);
|
|
88
96
|
if (slugMatch) {
|
|
89
97
|
await ensureRepo(slugMatch[1]);
|
|
90
|
-
await
|
|
91
|
-
body: {
|
|
92
|
-
service: "opencode-gitloops",
|
|
93
|
-
level: "info",
|
|
94
|
-
message: `Pre-warmed repo: ${slugMatch[1]}`,
|
|
95
|
-
},
|
|
96
|
-
});
|
|
98
|
+
await logger.info(`Pre-warmed repo: ${slugMatch[1]}`);
|
|
97
99
|
}
|
|
98
100
|
}
|
|
99
101
|
catch {
|
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/logger.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
type LogLevel = "debug" | "info" | "warn" | "error";
|
|
2
|
+
interface LogClient {
|
|
3
|
+
app: {
|
|
4
|
+
log(opts: {
|
|
5
|
+
body: {
|
|
6
|
+
service: string;
|
|
7
|
+
level: LogLevel;
|
|
8
|
+
message: string;
|
|
9
|
+
extra?: Record<string, unknown>;
|
|
10
|
+
};
|
|
11
|
+
}): Promise<unknown>;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Initialize the logger with the OpenCode plugin client.
|
|
16
|
+
* Call this once during plugin setup.
|
|
17
|
+
*/
|
|
18
|
+
export declare function initLogger(client: LogClient): void;
|
|
19
|
+
export declare const logger: {
|
|
20
|
+
debug: (message: string, extra?: Record<string, unknown>) => Promise<void>;
|
|
21
|
+
info: (message: string, extra?: Record<string, unknown>) => Promise<void>;
|
|
22
|
+
warn: (message: string, extra?: Record<string, unknown>) => Promise<void>;
|
|
23
|
+
error: (message: string, extra?: Record<string, unknown>) => Promise<void>;
|
|
24
|
+
};
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAEA,KAAK,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;AAEnD,UAAU,SAAS;IACjB,GAAG,EAAE;QACH,GAAG,CAAC,IAAI,EAAE;YACR,IAAI,EAAE;gBACJ,OAAO,EAAE,MAAM,CAAA;gBACf,KAAK,EAAE,QAAQ,CAAA;gBACf,OAAO,EAAE,MAAM,CAAA;gBACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;aAChC,CAAA;SACF,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;KACrB,CAAA;CACF;AAID;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAElD;AAqBD,eAAO,MAAM,MAAM;qBACA,MAAM,UAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;oBAExC,MAAM,UAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;oBAEvC,MAAM,UAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;qBAEtC,MAAM,UAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAEzD,CAAA"}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const SERVICE = "opencode-gitloops";
|
|
2
|
+
let _client = null;
|
|
3
|
+
/**
|
|
4
|
+
* Initialize the logger with the OpenCode plugin client.
|
|
5
|
+
* Call this once during plugin setup.
|
|
6
|
+
*/
|
|
7
|
+
export function initLogger(client) {
|
|
8
|
+
_client = client;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Log a structured message via the OpenCode SDK.
|
|
12
|
+
* Silently no-ops if the logger hasn't been initialized yet.
|
|
13
|
+
*/
|
|
14
|
+
async function log(level, message, extra) {
|
|
15
|
+
if (!_client)
|
|
16
|
+
return;
|
|
17
|
+
try {
|
|
18
|
+
await _client.app.log({
|
|
19
|
+
body: { service: SERVICE, level, message, ...(extra ? { extra } : {}) },
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// Never let logging failures propagate
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export const logger = {
|
|
27
|
+
debug: (message, extra) => log("debug", message, extra),
|
|
28
|
+
info: (message, extra) => log("info", message, extra),
|
|
29
|
+
warn: (message, extra) => log("warn", message, extra),
|
|
30
|
+
error: (message, extra) => log("error", message, extra),
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,GAAG,mBAAmB,CAAA;AAiBnC,IAAI,OAAO,GAAqB,IAAI,CAAA;AAEpC;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,MAAiB;IAC1C,OAAO,GAAG,MAAM,CAAA;AAClB,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,GAAG,CAChB,KAAe,EACf,OAAe,EACf,KAA+B;IAE/B,IAAI,CAAC,OAAO;QAAE,OAAM;IACpB,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;YACpB,IAAI,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE;SACxE,CAAC,CAAA;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,uCAAuC;IACzC,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,KAAK,EAAE,CAAC,OAAe,EAAE,KAA+B,EAAE,EAAE,CAC1D,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC;IAC9B,IAAI,EAAE,CAAC,OAAe,EAAE,KAA+B,EAAE,EAAE,CACzD,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC;IAC7B,IAAI,EAAE,CAAC,OAAe,EAAE,KAA+B,EAAE,EAAE,CACzD,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC;IAC7B,KAAK,EAAE,CAAC,OAAe,EAAE,KAA+B,EAAE,EAAE,CAC1D,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC;CAC/B,CAAA"}
|
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":"AAOA,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,CAwFjE;AAED;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAgD7D"}
|
package/dist/repo-manager.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
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
|
+
import { logger } from "./logger";
|
|
6
7
|
/**
|
|
7
8
|
* Parse a repo slug or URL into its components.
|
|
8
9
|
*
|
|
@@ -52,9 +53,10 @@ export function parseRepoSlug(input) {
|
|
|
52
53
|
/**
|
|
53
54
|
* Get the local filesystem path where a repo is (or would be) cached.
|
|
54
55
|
*/
|
|
55
|
-
export function getLocalPath(slug) {
|
|
56
|
+
export async function getLocalPath(slug) {
|
|
56
57
|
const { owner, repo } = parseRepoSlug(slug);
|
|
57
|
-
|
|
58
|
+
const config = await getConfig();
|
|
59
|
+
return path.join(config.cache_loc, owner, repo);
|
|
58
60
|
}
|
|
59
61
|
/**
|
|
60
62
|
* Check if a directory exists.
|
|
@@ -84,14 +86,25 @@ async function getLastCommit(repoPath) {
|
|
|
84
86
|
* Clone a repo (or fetch latest if already cloned). Returns metadata about the repo.
|
|
85
87
|
*
|
|
86
88
|
* Respects GITLOOPS_FULL_CLONE env var — if truthy, clones without --depth=1.
|
|
89
|
+
* Enforces max_repos limit via the configured eviction strategy after cloning.
|
|
87
90
|
*/
|
|
88
91
|
export async function ensureRepo(input) {
|
|
89
92
|
const parsed = parseRepoSlug(input);
|
|
90
|
-
|
|
93
|
+
await logger.debug(`Parsed repo identifier: ${parsed.slug}`, {
|
|
94
|
+
input,
|
|
95
|
+
owner: parsed.owner,
|
|
96
|
+
repo: parsed.repo,
|
|
97
|
+
});
|
|
98
|
+
const config = await getConfig();
|
|
99
|
+
const localPath = path.join(config.cache_loc, parsed.owner, parsed.repo);
|
|
91
100
|
const fullClone = process.env.GITLOOPS_FULL_CLONE === "true";
|
|
92
101
|
const depthArgs = fullClone ? [] : ["--depth=1"];
|
|
93
102
|
if (await dirExists(path.join(localPath, ".git"))) {
|
|
94
103
|
// Repo already cloned — fetch latest
|
|
104
|
+
await logger.info(`Fetching updates for ${parsed.slug}`, {
|
|
105
|
+
path: localPath,
|
|
106
|
+
fullClone,
|
|
107
|
+
});
|
|
95
108
|
try {
|
|
96
109
|
if (fullClone) {
|
|
97
110
|
await $ `git -C ${localPath} fetch origin`.quiet();
|
|
@@ -100,26 +113,49 @@ export async function ensureRepo(input) {
|
|
|
100
113
|
await $ `git -C ${localPath} fetch --depth=1 origin`.quiet();
|
|
101
114
|
}
|
|
102
115
|
await $ `git -C ${localPath} reset --hard origin/HEAD`.quiet();
|
|
116
|
+
await logger.info(`Updated repo: ${parsed.slug}`);
|
|
103
117
|
}
|
|
104
118
|
catch (err) {
|
|
119
|
+
await logger.error(`Failed to fetch updates for ${parsed.slug}`, {
|
|
120
|
+
error: err?.message || String(err),
|
|
121
|
+
});
|
|
105
122
|
throw new Error(`Failed to fetch updates for ${parsed.slug}: ${err.message || err}`);
|
|
106
123
|
}
|
|
107
124
|
}
|
|
108
125
|
else {
|
|
109
126
|
// Fresh clone
|
|
127
|
+
await logger.info(`Cloning ${parsed.slug}`, {
|
|
128
|
+
url: parsed.cloneUrl,
|
|
129
|
+
path: localPath,
|
|
130
|
+
fullClone,
|
|
131
|
+
});
|
|
132
|
+
const startTime = Date.now();
|
|
110
133
|
await fs.mkdir(path.dirname(localPath), { recursive: true });
|
|
111
134
|
try {
|
|
112
135
|
await $ `git clone ${depthArgs} ${parsed.cloneUrl} ${localPath}`.quiet();
|
|
136
|
+
const duration = Date.now() - startTime;
|
|
137
|
+
await logger.info(`Cloned repo: ${parsed.slug} (${duration}ms)`, {
|
|
138
|
+
path: localPath,
|
|
139
|
+
durationMs: duration,
|
|
140
|
+
});
|
|
113
141
|
}
|
|
114
142
|
catch (err) {
|
|
115
143
|
// Clean up partial clone on failure
|
|
116
144
|
await fs.rm(localPath, { recursive: true, force: true }).catch(() => { });
|
|
117
145
|
if (String(err).includes("not found") ||
|
|
118
146
|
String(err).includes("Repository not found")) {
|
|
147
|
+
await logger.error(`Repository not found: ${parsed.slug}`, {
|
|
148
|
+
url: parsed.cloneUrl,
|
|
149
|
+
});
|
|
119
150
|
throw new Error(`Repository "${parsed.slug}" not found on GitHub. Only public repos are supported in v1.`);
|
|
120
151
|
}
|
|
152
|
+
await logger.error(`Failed to clone ${parsed.slug}`, {
|
|
153
|
+
error: err?.message || String(err),
|
|
154
|
+
});
|
|
121
155
|
throw new Error(`Failed to clone ${parsed.slug}: ${err.message || err}`);
|
|
122
156
|
}
|
|
157
|
+
// Evict old repos if we've exceeded the max
|
|
158
|
+
await evictIfNeeded(config, parsed.slug);
|
|
123
159
|
}
|
|
124
160
|
const lastCommit = await getLastCommit(localPath);
|
|
125
161
|
return {
|
|
@@ -132,22 +168,24 @@ export async function ensureRepo(input) {
|
|
|
132
168
|
};
|
|
133
169
|
}
|
|
134
170
|
/**
|
|
135
|
-
* List all repos currently cached under
|
|
171
|
+
* List all repos currently cached under the configured cache location.
|
|
136
172
|
*/
|
|
137
173
|
export async function listCachedRepos() {
|
|
174
|
+
const config = await getConfig();
|
|
175
|
+
const cacheLoc = config.cache_loc;
|
|
138
176
|
const repos = [];
|
|
139
|
-
if (!(await dirExists(
|
|
177
|
+
if (!(await dirExists(cacheLoc))) {
|
|
140
178
|
return repos;
|
|
141
179
|
}
|
|
142
180
|
let owners;
|
|
143
181
|
try {
|
|
144
|
-
owners = await fs.readdir(
|
|
182
|
+
owners = await fs.readdir(cacheLoc);
|
|
145
183
|
}
|
|
146
184
|
catch {
|
|
147
185
|
return repos;
|
|
148
186
|
}
|
|
149
187
|
for (const owner of owners) {
|
|
150
|
-
const ownerPath = path.join(
|
|
188
|
+
const ownerPath = path.join(cacheLoc, owner);
|
|
151
189
|
if (!(await dirExists(ownerPath)))
|
|
152
190
|
continue;
|
|
153
191
|
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/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,cAAc;;;;;;;;;;CA0DzB,CAAA;AAEF,eAAO,MAAM,gBAAgB;;;;;;;;CA0B3B,CAAA;AAEF,eAAO,MAAM,aAAa;;;;CAqBxB,CAAA"}
|
package/dist/tools.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { tool } from "@opencode-ai/plugin";
|
|
2
2
|
import { ensureRepo, listCachedRepos } from "./repo-manager";
|
|
3
|
+
import { logger } from "./logger";
|
|
3
4
|
export const gitloops_clone = tool({
|
|
4
5
|
description: "Clone or ensure a GitHub repo is available locally for exploration. " +
|
|
5
6
|
"Returns the local filesystem path to use with read/grep/glob/list tools. " +
|
|
@@ -14,19 +15,30 @@ export const gitloops_clone = tool({
|
|
|
14
15
|
.describe("Branch to checkout after cloning (default: repo default branch)"),
|
|
15
16
|
},
|
|
16
17
|
async execute(args) {
|
|
18
|
+
await logger.info("Tool invoked: gitloops_clone", {
|
|
19
|
+
repo: args.repo,
|
|
20
|
+
branch: args.branch ?? null,
|
|
21
|
+
});
|
|
17
22
|
const info = await ensureRepo(args.repo);
|
|
18
23
|
// If a specific branch was requested, check it out
|
|
19
24
|
if (args.branch) {
|
|
25
|
+
await logger.info(`Checking out branch "${args.branch}" for ${info.slug}`);
|
|
20
26
|
const { $ } = await import("bun");
|
|
21
27
|
try {
|
|
22
28
|
await $ `git -C ${info.localPath} fetch origin ${args.branch}`.quiet();
|
|
23
29
|
await $ `git -C ${info.localPath} checkout ${args.branch}`.quiet();
|
|
24
30
|
await $ `git -C ${info.localPath} reset --hard origin/${args.branch}`.quiet();
|
|
31
|
+
await logger.info(`Checked out branch "${args.branch}" for ${info.slug}`);
|
|
25
32
|
}
|
|
26
33
|
catch (err) {
|
|
34
|
+
await logger.error(`Failed to checkout branch "${args.branch}" for ${info.slug}`, { error: err?.message || String(err) });
|
|
27
35
|
throw new Error(`Failed to checkout branch "${args.branch}" for ${info.slug}: ${err.message || err}`);
|
|
28
36
|
}
|
|
29
37
|
}
|
|
38
|
+
await logger.debug("gitloops_clone complete", {
|
|
39
|
+
slug: info.slug,
|
|
40
|
+
path: info.localPath,
|
|
41
|
+
});
|
|
30
42
|
return [
|
|
31
43
|
`Repository: ${info.slug}`,
|
|
32
44
|
`Local path: ${info.localPath}`,
|
|
@@ -46,7 +58,12 @@ export const gitloops_refresh = tool({
|
|
|
46
58
|
.describe("Repo identifier — e.g. 'facebook/react'"),
|
|
47
59
|
},
|
|
48
60
|
async execute(args) {
|
|
61
|
+
await logger.info("Tool invoked: gitloops_refresh", { repo: args.repo });
|
|
49
62
|
const info = await ensureRepo(args.repo);
|
|
63
|
+
await logger.debug("gitloops_refresh complete", {
|
|
64
|
+
slug: info.slug,
|
|
65
|
+
commit: info.lastCommit,
|
|
66
|
+
});
|
|
50
67
|
return [
|
|
51
68
|
`Refreshed: ${info.slug}`,
|
|
52
69
|
`Local path: ${info.localPath}`,
|
|
@@ -60,7 +77,9 @@ export const gitloops_list = tool({
|
|
|
60
77
|
"Shows slug, local path, and last modified time for each cached repo.",
|
|
61
78
|
args: {},
|
|
62
79
|
async execute() {
|
|
80
|
+
await logger.info("Tool invoked: gitloops_list");
|
|
63
81
|
const repos = await listCachedRepos();
|
|
82
|
+
await logger.debug("gitloops_list complete", { count: repos.length });
|
|
64
83
|
if (repos.length === 0) {
|
|
65
84
|
return "No repos cached. Use gitloops_clone to clone a repo first.";
|
|
66
85
|
}
|
package/dist/tools.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAA;AAC1C,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.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.1
|
|
3
|
+
"version": "0.2.1",
|
|
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",
|
|
@@ -24,6 +25,7 @@
|
|
|
24
25
|
],
|
|
25
26
|
"scripts": {
|
|
26
27
|
"build": "bun run tsc",
|
|
28
|
+
"typecheck": "tsc --noEmit",
|
|
27
29
|
"prepublishOnly": "bun run build"
|
|
28
30
|
},
|
|
29
31
|
"dependencies": {
|
|
@@ -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
|
+
}
|