@link-assistant/agent 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/EXAMPLES.md +4 -4
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/bun/index.ts +28 -2
- package/src/config/config.ts +148 -18
- package/src/flag/flag.ts +57 -11
- package/src/global/index.ts +2 -1
- package/src/index.js +18 -0
- package/src/provider/provider.ts +41 -8
package/EXAMPLES.md
CHANGED
|
@@ -211,7 +211,7 @@ echo '{"message":"search web","tools":[{"name":"websearch","params":{"query":"Re
|
|
|
211
211
|
**opencode (requires OPENCODE_EXPERIMENTAL_EXA=true):**
|
|
212
212
|
|
|
213
213
|
```bash
|
|
214
|
-
echo '{"message":"search web","tools":[{"name":"websearch","params":{"query":"TypeScript latest features"}}]}' |
|
|
214
|
+
echo '{"message":"search web","tools":[{"name":"websearch","params":{"query":"TypeScript latest features"}}]}' | opencode run --format json --model opencode/grok-code
|
|
215
215
|
```
|
|
216
216
|
|
|
217
217
|
### codesearch Tool
|
|
@@ -229,7 +229,7 @@ echo '{"message":"search code","tools":[{"name":"codesearch","params":{"query":"
|
|
|
229
229
|
**opencode (requires OPENCODE_EXPERIMENTAL_EXA=true):**
|
|
230
230
|
|
|
231
231
|
```bash
|
|
232
|
-
echo '{"message":"search code","tools":[{"name":"codesearch","params":{"query":"React hooks implementation"}}]}' |
|
|
232
|
+
echo '{"message":"search code","tools":[{"name":"codesearch","params":{"query":"React hooks implementation"}}]}' | opencode run --format json --model opencode/grok-code
|
|
233
233
|
```
|
|
234
234
|
|
|
235
235
|
## Execution Tools
|
|
@@ -248,8 +248,8 @@ echo '{"message":"run batch","tools":[{"name":"batch","params":{"tool_calls":[{"
|
|
|
248
248
|
|
|
249
249
|
```bash
|
|
250
250
|
# Create config file first
|
|
251
|
-
mkdir -p .
|
|
252
|
-
echo '{"experimental":{"batch_tool":true}}' > .opencode
|
|
251
|
+
mkdir -p .link-assistant-agent
|
|
252
|
+
echo '{"experimental":{"batch_tool":true}}' > .link-assistant-agent/opencode.json
|
|
253
253
|
|
|
254
254
|
# Then run
|
|
255
255
|
echo '{"message":"run batch","tools":[{"name":"batch","params":{"tool_calls":[{"tool":"bash","parameters":{"command":"echo hello"}},{"tool":"bash","parameters":{"command":"echo world"}}]}}]}' | opencode run --format json --model opencode/grok-code
|
package/README.md
CHANGED
|
@@ -314,7 +314,7 @@ agent mcp add playwright npx @playwright/mcp@latest
|
|
|
314
314
|
agent mcp list
|
|
315
315
|
```
|
|
316
316
|
|
|
317
|
-
This will create a configuration file at `~/.config/
|
|
317
|
+
This will create a configuration file at `~/.config/link-assistant-agent/opencode.json` (or your system's config directory) with:
|
|
318
318
|
|
|
319
319
|
```json
|
|
320
320
|
{
|
|
@@ -477,7 +477,7 @@ The package publishes source files directly (no build step required). Bun handle
|
|
|
477
477
|
|
|
478
478
|
### No Configuration Required
|
|
479
479
|
|
|
480
|
-
- **WebSearch/CodeSearch**: Work without `
|
|
480
|
+
- **WebSearch/CodeSearch**: Work without `LINK_ASSISTANT_AGENT_EXPERIMENTAL_EXA` environment variable (legacy `OPENCODE_EXPERIMENTAL_EXA` still supported)
|
|
481
481
|
- **Batch Tool**: Always enabled, no experimental flag needed
|
|
482
482
|
- **All Tools**: No config files, API keys handled automatically
|
|
483
483
|
|
package/package.json
CHANGED
package/src/bun/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { Log } from '../util/log';
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { NamedError } from '../util/error';
|
|
6
6
|
import { readableStreamToText } from 'bun';
|
|
7
|
+
import { Flag } from '../flag/flag';
|
|
7
8
|
|
|
8
9
|
export namespace BunProc {
|
|
9
10
|
const log = Log.create({ service: 'bun' });
|
|
@@ -43,7 +44,10 @@ export namespace BunProc {
|
|
|
43
44
|
stderr,
|
|
44
45
|
});
|
|
45
46
|
if (code !== 0) {
|
|
46
|
-
|
|
47
|
+
const parts = [`Command failed with exit code ${result.exitCode}`];
|
|
48
|
+
if (stderr) parts.push(`stderr: ${stderr}`);
|
|
49
|
+
if (stdout) parts.push(`stdout: ${stdout}`);
|
|
50
|
+
throw new Error(parts.join('\n'));
|
|
47
51
|
}
|
|
48
52
|
return result;
|
|
49
53
|
}
|
|
@@ -57,6 +61,7 @@ export namespace BunProc {
|
|
|
57
61
|
z.object({
|
|
58
62
|
pkg: z.string(),
|
|
59
63
|
version: z.string(),
|
|
64
|
+
details: z.string().optional(),
|
|
60
65
|
})
|
|
61
66
|
);
|
|
62
67
|
|
|
@@ -70,6 +75,20 @@ export namespace BunProc {
|
|
|
70
75
|
});
|
|
71
76
|
if (parsed.dependencies[pkg] === version) return mod;
|
|
72
77
|
|
|
78
|
+
// Check for dry-run mode
|
|
79
|
+
if (Flag.OPENCODE_DRY_RUN) {
|
|
80
|
+
log.info(
|
|
81
|
+
'[DRY RUN] Would install package (skipping actual installation)',
|
|
82
|
+
{
|
|
83
|
+
pkg,
|
|
84
|
+
version,
|
|
85
|
+
targetPath: mod,
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
// In dry-run mode, pretend the package is installed
|
|
89
|
+
return mod;
|
|
90
|
+
}
|
|
91
|
+
|
|
73
92
|
// Build command arguments
|
|
74
93
|
const args = [
|
|
75
94
|
'add',
|
|
@@ -92,13 +111,20 @@ export namespace BunProc {
|
|
|
92
111
|
await BunProc.run(args, {
|
|
93
112
|
cwd: Global.Path.cache,
|
|
94
113
|
}).catch((e) => {
|
|
114
|
+
log.error('package installation failed', {
|
|
115
|
+
pkg,
|
|
116
|
+
version,
|
|
117
|
+
error: e instanceof Error ? e.message : String(e),
|
|
118
|
+
stack: e instanceof Error ? e.stack : undefined,
|
|
119
|
+
});
|
|
95
120
|
throw new InstallFailedError(
|
|
96
|
-
{ pkg, version },
|
|
121
|
+
{ pkg, version, details: e instanceof Error ? e.message : String(e) },
|
|
97
122
|
{
|
|
98
123
|
cause: e,
|
|
99
124
|
}
|
|
100
125
|
);
|
|
101
126
|
});
|
|
127
|
+
log.info('package installed successfully', { pkg, version });
|
|
102
128
|
parsed.dependencies[pkg] = version;
|
|
103
129
|
await Bun.write(pkgjson.name!, JSON.stringify(parsed, null, 2));
|
|
104
130
|
return mod;
|
package/src/config/config.ts
CHANGED
|
@@ -22,6 +22,103 @@ import { ConfigMarkdown } from './markdown';
|
|
|
22
22
|
export namespace Config {
|
|
23
23
|
const log = Log.create({ service: 'config' });
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Automatically migrate .opencode directories to .link-assistant-agent
|
|
27
|
+
* This ensures a smooth transition for both file system configs and environment variables.
|
|
28
|
+
* Once .link-assistant-agent exists, we stop reading from .opencode.
|
|
29
|
+
*/
|
|
30
|
+
async function migrateConfigDirectories() {
|
|
31
|
+
// Find all .opencode and .link-assistant-agent directories in the project hierarchy
|
|
32
|
+
const allDirs = await Array.fromAsync(
|
|
33
|
+
Filesystem.up({
|
|
34
|
+
targets: ['.link-assistant-agent', '.opencode'],
|
|
35
|
+
start: Instance.directory,
|
|
36
|
+
stop: Instance.worktree,
|
|
37
|
+
})
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const newConfigDirs = allDirs.filter((d) =>
|
|
41
|
+
d.endsWith('.link-assistant-agent')
|
|
42
|
+
);
|
|
43
|
+
const oldConfigDirs = allDirs.filter((d) => d.endsWith('.opencode'));
|
|
44
|
+
|
|
45
|
+
// For each old config directory, check if there's a corresponding new one
|
|
46
|
+
for (const oldDir of oldConfigDirs) {
|
|
47
|
+
const parentDir = path.dirname(oldDir);
|
|
48
|
+
const newDir = path.join(parentDir, '.link-assistant-agent');
|
|
49
|
+
|
|
50
|
+
// Check if the new directory already exists at the same level
|
|
51
|
+
const newDirExists = newConfigDirs.includes(newDir);
|
|
52
|
+
|
|
53
|
+
if (!newDirExists) {
|
|
54
|
+
try {
|
|
55
|
+
// Perform migration by copying the entire directory
|
|
56
|
+
log.info(
|
|
57
|
+
`Migrating config from ${oldDir} to ${newDir} for smooth transition`
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// Use fs-extra style recursive copy
|
|
61
|
+
await copyDirectory(oldDir, newDir);
|
|
62
|
+
|
|
63
|
+
log.info(`Successfully migrated config to ${newDir}`);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
log.error(`Failed to migrate config from ${oldDir}:`, error);
|
|
66
|
+
// Don't throw - allow the app to continue with the old config
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Also migrate global config if needed
|
|
72
|
+
const oldGlobalPath = path.join(os.homedir(), '.config', 'opencode');
|
|
73
|
+
const newGlobalPath = Global.Path.config;
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const oldGlobalExists = await fs
|
|
77
|
+
.stat(oldGlobalPath)
|
|
78
|
+
.then(() => true)
|
|
79
|
+
.catch(() => false);
|
|
80
|
+
const newGlobalExists = await fs
|
|
81
|
+
.stat(newGlobalPath)
|
|
82
|
+
.then(() => true)
|
|
83
|
+
.catch(() => false);
|
|
84
|
+
|
|
85
|
+
if (oldGlobalExists && !newGlobalExists) {
|
|
86
|
+
log.info(
|
|
87
|
+
`Migrating global config from ${oldGlobalPath} to ${newGlobalPath}`
|
|
88
|
+
);
|
|
89
|
+
await copyDirectory(oldGlobalPath, newGlobalPath);
|
|
90
|
+
log.info(`Successfully migrated global config to ${newGlobalPath}`);
|
|
91
|
+
}
|
|
92
|
+
} catch (error) {
|
|
93
|
+
log.error('Failed to migrate global config:', error);
|
|
94
|
+
// Don't throw - allow the app to continue
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Recursively copy a directory and all its contents
|
|
100
|
+
*/
|
|
101
|
+
async function copyDirectory(src: string, dest: string) {
|
|
102
|
+
// Create destination directory
|
|
103
|
+
await fs.mkdir(dest, { recursive: true });
|
|
104
|
+
|
|
105
|
+
// Read all entries in source directory
|
|
106
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
107
|
+
|
|
108
|
+
for (const entry of entries) {
|
|
109
|
+
const srcPath = path.join(src, entry.name);
|
|
110
|
+
const destPath = path.join(dest, entry.name);
|
|
111
|
+
|
|
112
|
+
if (entry.isDirectory()) {
|
|
113
|
+
// Recursively copy subdirectories
|
|
114
|
+
await copyDirectory(srcPath, destPath);
|
|
115
|
+
} else if (entry.isFile() || entry.isSymbolicLink()) {
|
|
116
|
+
// Copy files
|
|
117
|
+
await fs.copyFile(srcPath, destPath);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
25
122
|
export const state = Instance.state(async () => {
|
|
26
123
|
const auth = await Auth.all();
|
|
27
124
|
let result = await global();
|
|
@@ -64,20 +161,43 @@ export namespace Config {
|
|
|
64
161
|
result.agent = result.agent || {};
|
|
65
162
|
result.mode = result.mode || {};
|
|
66
163
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
164
|
+
// Perform automatic migration from .opencode to .link-assistant-agent if needed
|
|
165
|
+
await migrateConfigDirectories();
|
|
166
|
+
|
|
167
|
+
// Find all config directories
|
|
168
|
+
const foundDirs = await Array.fromAsync(
|
|
169
|
+
Filesystem.up({
|
|
170
|
+
targets: ['.link-assistant-agent', '.opencode'],
|
|
171
|
+
start: Instance.directory,
|
|
172
|
+
stop: Instance.worktree,
|
|
173
|
+
})
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
// Check if any .link-assistant-agent directory exists
|
|
177
|
+
const hasNewConfig = foundDirs.some((d) =>
|
|
178
|
+
d.endsWith('.link-assistant-agent')
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
// Filter out .opencode directories if .link-assistant-agent exists
|
|
182
|
+
const filteredDirs = foundDirs.filter((dir) => {
|
|
183
|
+
// If .link-assistant-agent exists, exclude .opencode directories
|
|
184
|
+
if (hasNewConfig && dir.endsWith('.opencode')) {
|
|
185
|
+
log.debug(
|
|
186
|
+
'Skipping .opencode directory (using .link-assistant-agent):',
|
|
187
|
+
{
|
|
188
|
+
path: dir,
|
|
189
|
+
}
|
|
190
|
+
);
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
return true;
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const directories = [Global.Path.config, ...filteredDirs];
|
|
77
197
|
|
|
78
198
|
if (Flag.OPENCODE_CONFIG_DIR) {
|
|
79
199
|
directories.push(Flag.OPENCODE_CONFIG_DIR);
|
|
80
|
-
log.debug('loading config from
|
|
200
|
+
log.debug('loading config from LINK_ASSISTANT_AGENT_CONFIG_DIR', {
|
|
81
201
|
path: Flag.OPENCODE_CONFIG_DIR,
|
|
82
202
|
});
|
|
83
203
|
}
|
|
@@ -86,7 +206,11 @@ export namespace Config {
|
|
|
86
206
|
for (const dir of directories) {
|
|
87
207
|
await assertValid(dir);
|
|
88
208
|
|
|
89
|
-
if (
|
|
209
|
+
if (
|
|
210
|
+
dir.endsWith('.link-assistant-agent') ||
|
|
211
|
+
dir.endsWith('.opencode') ||
|
|
212
|
+
dir === Flag.OPENCODE_CONFIG_DIR
|
|
213
|
+
) {
|
|
90
214
|
for (const file of ['opencode.jsonc', 'opencode.json']) {
|
|
91
215
|
log.debug(`loading config from ${path.join(dir, file)}`);
|
|
92
216
|
result = mergeDeep(result, await loadFile(path.join(dir, file)));
|
|
@@ -162,7 +286,11 @@ export namespace Config {
|
|
|
162
286
|
if (!md.data) continue;
|
|
163
287
|
|
|
164
288
|
const name = (() => {
|
|
165
|
-
const patterns = [
|
|
289
|
+
const patterns = [
|
|
290
|
+
'/.link-assistant-agent/command/',
|
|
291
|
+
'/.opencode/command/',
|
|
292
|
+
'/command/',
|
|
293
|
+
];
|
|
166
294
|
const pattern = patterns.find((p) => item.includes(p));
|
|
167
295
|
|
|
168
296
|
if (pattern) {
|
|
@@ -202,11 +330,13 @@ export namespace Config {
|
|
|
202
330
|
|
|
203
331
|
// Extract relative path from agent folder for nested agents
|
|
204
332
|
let agentName = path.basename(item, '.md');
|
|
205
|
-
const agentFolderPath = item.includes('/.
|
|
206
|
-
? item.split('/.
|
|
207
|
-
: item.includes('/agent/')
|
|
208
|
-
? item.split('/agent/')[1]
|
|
209
|
-
:
|
|
333
|
+
const agentFolderPath = item.includes('/.link-assistant-agent/agent/')
|
|
334
|
+
? item.split('/.link-assistant-agent/agent/')[1]
|
|
335
|
+
: item.includes('/.opencode/agent/')
|
|
336
|
+
? item.split('/.opencode/agent/')[1]
|
|
337
|
+
: item.includes('/agent/')
|
|
338
|
+
? item.split('/agent/')[1]
|
|
339
|
+
: agentName + '.md';
|
|
210
340
|
|
|
211
341
|
// If agent is in a subfolder, include folder path in name
|
|
212
342
|
if (agentFolderPath.includes('/')) {
|
package/src/flag/flag.ts
CHANGED
|
@@ -1,32 +1,78 @@
|
|
|
1
1
|
export namespace Flag {
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
// Helper to check env vars with new prefix first, then fall back to old prefix for backwards compatibility
|
|
3
|
+
function getEnv(newKey: string, oldKey: string): string | undefined {
|
|
4
|
+
return process.env[newKey] ?? process.env[oldKey];
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function truthyCompat(newKey: string, oldKey: string): boolean {
|
|
8
|
+
const value = (getEnv(newKey, oldKey) ?? '').toLowerCase();
|
|
9
|
+
return value === 'true' || value === '1';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// LINK_ASSISTANT_AGENT_AUTO_SHARE removed - no sharing support
|
|
13
|
+
export const OPENCODE_CONFIG = getEnv(
|
|
14
|
+
'LINK_ASSISTANT_AGENT_CONFIG',
|
|
15
|
+
'OPENCODE_CONFIG'
|
|
16
|
+
);
|
|
17
|
+
export const OPENCODE_CONFIG_DIR = getEnv(
|
|
18
|
+
'LINK_ASSISTANT_AGENT_CONFIG_DIR',
|
|
19
|
+
'OPENCODE_CONFIG_DIR'
|
|
20
|
+
);
|
|
21
|
+
export const OPENCODE_CONFIG_CONTENT = getEnv(
|
|
22
|
+
'LINK_ASSISTANT_AGENT_CONFIG_CONTENT',
|
|
23
|
+
'OPENCODE_CONFIG_CONTENT'
|
|
24
|
+
);
|
|
25
|
+
export const OPENCODE_DISABLE_AUTOUPDATE = truthyCompat(
|
|
26
|
+
'LINK_ASSISTANT_AGENT_DISABLE_AUTOUPDATE',
|
|
7
27
|
'OPENCODE_DISABLE_AUTOUPDATE'
|
|
8
28
|
);
|
|
9
|
-
export const OPENCODE_DISABLE_PRUNE =
|
|
10
|
-
|
|
29
|
+
export const OPENCODE_DISABLE_PRUNE = truthyCompat(
|
|
30
|
+
'LINK_ASSISTANT_AGENT_DISABLE_PRUNE',
|
|
31
|
+
'OPENCODE_DISABLE_PRUNE'
|
|
32
|
+
);
|
|
33
|
+
export const OPENCODE_ENABLE_EXPERIMENTAL_MODELS = truthyCompat(
|
|
34
|
+
'LINK_ASSISTANT_AGENT_ENABLE_EXPERIMENTAL_MODELS',
|
|
11
35
|
'OPENCODE_ENABLE_EXPERIMENTAL_MODELS'
|
|
12
36
|
);
|
|
13
|
-
export const OPENCODE_DISABLE_AUTOCOMPACT =
|
|
37
|
+
export const OPENCODE_DISABLE_AUTOCOMPACT = truthyCompat(
|
|
38
|
+
'LINK_ASSISTANT_AGENT_DISABLE_AUTOCOMPACT',
|
|
14
39
|
'OPENCODE_DISABLE_AUTOCOMPACT'
|
|
15
40
|
);
|
|
16
41
|
|
|
17
42
|
// Experimental
|
|
18
|
-
export const OPENCODE_EXPERIMENTAL =
|
|
43
|
+
export const OPENCODE_EXPERIMENTAL = truthyCompat(
|
|
44
|
+
'LINK_ASSISTANT_AGENT_EXPERIMENTAL',
|
|
45
|
+
'OPENCODE_EXPERIMENTAL'
|
|
46
|
+
);
|
|
19
47
|
export const OPENCODE_EXPERIMENTAL_WATCHER =
|
|
20
|
-
OPENCODE_EXPERIMENTAL ||
|
|
48
|
+
OPENCODE_EXPERIMENTAL ||
|
|
49
|
+
truthyCompat(
|
|
50
|
+
'LINK_ASSISTANT_AGENT_EXPERIMENTAL_WATCHER',
|
|
51
|
+
'OPENCODE_EXPERIMENTAL_WATCHER'
|
|
52
|
+
);
|
|
21
53
|
|
|
22
54
|
// Verbose mode - enables detailed logging of API requests
|
|
23
|
-
export let OPENCODE_VERBOSE =
|
|
55
|
+
export let OPENCODE_VERBOSE = truthyCompat(
|
|
56
|
+
'LINK_ASSISTANT_AGENT_VERBOSE',
|
|
57
|
+
'OPENCODE_VERBOSE'
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// Dry run mode - simulate operations without making actual API calls or changes
|
|
61
|
+
export let OPENCODE_DRY_RUN = truthyCompat(
|
|
62
|
+
'LINK_ASSISTANT_AGENT_DRY_RUN',
|
|
63
|
+
'OPENCODE_DRY_RUN'
|
|
64
|
+
);
|
|
24
65
|
|
|
25
66
|
// Allow setting verbose mode programmatically (e.g., from CLI --verbose flag)
|
|
26
67
|
export function setVerbose(value: boolean) {
|
|
27
68
|
OPENCODE_VERBOSE = value;
|
|
28
69
|
}
|
|
29
70
|
|
|
71
|
+
// Allow setting dry run mode programmatically (e.g., from CLI --dry-run flag)
|
|
72
|
+
export function setDryRun(value: boolean) {
|
|
73
|
+
OPENCODE_DRY_RUN = value;
|
|
74
|
+
}
|
|
75
|
+
|
|
30
76
|
function truthy(key: string) {
|
|
31
77
|
const value = process.env[key]?.toLowerCase();
|
|
32
78
|
return value === 'true' || value === '1';
|
package/src/global/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { xdgData, xdgCache, xdgConfig, xdgState } from 'xdg-basedir';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import os from 'os';
|
|
5
5
|
|
|
6
|
-
const app = '
|
|
6
|
+
const app = 'link-assistant-agent';
|
|
7
7
|
|
|
8
8
|
const data = path.join(xdgData!, app);
|
|
9
9
|
const cache = path.join(xdgCache!, app);
|
|
@@ -24,6 +24,7 @@ export namespace Global {
|
|
|
24
24
|
|
|
25
25
|
await Promise.all([
|
|
26
26
|
fs.mkdir(Global.Path.data, { recursive: true }),
|
|
27
|
+
fs.mkdir(Global.Path.cache, { recursive: true }),
|
|
27
28
|
fs.mkdir(Global.Path.config, { recursive: true }),
|
|
28
29
|
fs.mkdir(Global.Path.state, { recursive: true }),
|
|
29
30
|
fs.mkdir(Global.Path.log, { recursive: true }),
|
package/src/index.js
CHANGED
|
@@ -110,6 +110,13 @@ async function runAgentMode(argv) {
|
|
|
110
110
|
console.error(`Script path: ${import.meta.path}`);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
// Log dry-run mode if enabled
|
|
114
|
+
if (Flag.OPENCODE_DRY_RUN) {
|
|
115
|
+
console.error(
|
|
116
|
+
`[DRY RUN MODE] No actual API calls or package installations will be made`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
113
120
|
// Parse model argument (handle model IDs with slashes like groq/qwen/qwen3-32b)
|
|
114
121
|
const modelParts = argv.model.split('/');
|
|
115
122
|
let providerID = modelParts[0] || 'opencode';
|
|
@@ -574,6 +581,12 @@ async function main() {
|
|
|
574
581
|
'Enable verbose mode to debug API requests (shows system prompt, token counts, etc.)',
|
|
575
582
|
default: false,
|
|
576
583
|
})
|
|
584
|
+
.option('dry-run', {
|
|
585
|
+
type: 'boolean',
|
|
586
|
+
description:
|
|
587
|
+
'Simulate operations without making actual API calls or package installations (useful for testing)',
|
|
588
|
+
default: false,
|
|
589
|
+
})
|
|
577
590
|
.option('use-existing-claude-oauth', {
|
|
578
591
|
type: 'boolean',
|
|
579
592
|
description:
|
|
@@ -588,6 +601,11 @@ async function main() {
|
|
|
588
601
|
Flag.setVerbose(true);
|
|
589
602
|
}
|
|
590
603
|
|
|
604
|
+
// Set dry-run flag if requested
|
|
605
|
+
if (argv['dry-run']) {
|
|
606
|
+
Flag.setDryRun(true);
|
|
607
|
+
}
|
|
608
|
+
|
|
591
609
|
// Initialize logging system
|
|
592
610
|
// - If verbose: print logs to stderr for debugging
|
|
593
611
|
// - Otherwise: write logs to file to keep CLI output clean
|
package/src/provider/provider.ts
CHANGED
|
@@ -721,7 +721,17 @@ export namespace Provider {
|
|
|
721
721
|
|
|
722
722
|
let installedPath: string;
|
|
723
723
|
if (!pkg.startsWith('file://')) {
|
|
724
|
+
log.info('installing provider package', {
|
|
725
|
+
providerID: provider.id,
|
|
726
|
+
pkg,
|
|
727
|
+
version: 'latest',
|
|
728
|
+
});
|
|
724
729
|
installedPath = await BunProc.install(pkg, 'latest');
|
|
730
|
+
log.info('provider package installed successfully', {
|
|
731
|
+
providerID: provider.id,
|
|
732
|
+
pkg,
|
|
733
|
+
installedPath,
|
|
734
|
+
});
|
|
725
735
|
} else {
|
|
726
736
|
log.info('loading local provider', { pkg });
|
|
727
737
|
installedPath = pkg;
|
|
@@ -769,6 +779,13 @@ export namespace Provider {
|
|
|
769
779
|
s.sdk.set(key, loaded);
|
|
770
780
|
return loaded as SDK;
|
|
771
781
|
})().catch((e) => {
|
|
782
|
+
log.error('provider initialization failed', {
|
|
783
|
+
providerID: provider.id,
|
|
784
|
+
pkg: model.provider?.npm ?? provider.npm ?? provider.id,
|
|
785
|
+
error: e instanceof Error ? e.message : String(e),
|
|
786
|
+
stack: e instanceof Error ? e.stack : undefined,
|
|
787
|
+
cause: e instanceof Error && e.cause ? String(e.cause) : undefined,
|
|
788
|
+
});
|
|
772
789
|
throw new InitError({ providerID: provider.id }, { cause: e });
|
|
773
790
|
});
|
|
774
791
|
}
|
|
@@ -859,7 +876,13 @@ export namespace Provider {
|
|
|
859
876
|
}
|
|
860
877
|
}
|
|
861
878
|
|
|
862
|
-
const priority = [
|
|
879
|
+
const priority = [
|
|
880
|
+
'grok-code',
|
|
881
|
+
'gpt-5',
|
|
882
|
+
'claude-sonnet-4',
|
|
883
|
+
'big-pickle',
|
|
884
|
+
'gemini-3-pro',
|
|
885
|
+
];
|
|
863
886
|
export function sort(models: ModelsDev.Model[]) {
|
|
864
887
|
return sortBy(
|
|
865
888
|
models,
|
|
@@ -876,13 +899,23 @@ export namespace Provider {
|
|
|
876
899
|
const cfg = await Config.get();
|
|
877
900
|
if (cfg.model) return parseModel(cfg.model);
|
|
878
901
|
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
902
|
+
// Prefer opencode provider if available
|
|
903
|
+
const providers = await list().then((val) => Object.values(val));
|
|
904
|
+
const opencodeProvider = providers.find((p) => p.info.id === 'opencode');
|
|
905
|
+
if (opencodeProvider) {
|
|
906
|
+
const [model] = sort(Object.values(opencodeProvider.info.models));
|
|
907
|
+
if (model) {
|
|
908
|
+
return {
|
|
909
|
+
providerID: opencodeProvider.info.id,
|
|
910
|
+
modelID: model.id,
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// Fall back to any available provider
|
|
916
|
+
const provider = providers.find(
|
|
917
|
+
(p) => !cfg.provider || Object.keys(cfg.provider).includes(p.info.id)
|
|
918
|
+
);
|
|
886
919
|
if (!provider) throw new Error('no providers found');
|
|
887
920
|
const [model] = sort(Object.values(provider.info.models));
|
|
888
921
|
if (!model) throw new Error('no models found');
|