@outsidestudio/agent-kit 0.1.1 → 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 +2 -1
- package/package.json +1 -1
- package/registry/outside/llm.json +3 -3
- package/skills/shared/outside-commit-conventions/SKILL.md +3 -2
- package/src/adapters/omp.ts +10 -4
- package/src/adapters/opencode.ts +16 -5
- package/src/commands/install.ts +23 -6
- package/src/skills-install.ts +7 -3
- package/src/types.ts +5 -0
package/README.md
CHANGED
|
@@ -20,13 +20,14 @@ That command:
|
|
|
20
20
|
2. Asks once for any secrets the profile needs (API keys, model endpoint) and stores them in `~/.config/outside-agent-kit/env` (chmod 600), loaded from `~/.zshrc` / `~/.bashrc`.
|
|
21
21
|
3. Fetches the **latest** version of every skill and plugin. Nothing is pinned.
|
|
22
22
|
4. Writes each tool's config, keeping anything the developer added themselves.
|
|
23
|
+
5. Installing **only registry items** (no profile) is additive: it adds/replaces those items and keeps the rest of your install. Installing a profile reconciles the full set.
|
|
23
24
|
|
|
24
25
|
## Commands
|
|
25
26
|
|
|
26
27
|
| Command | What it does |
|
|
27
28
|
|---|---|
|
|
28
29
|
| `agent-kit <profile...>` | Install one or more profiles |
|
|
29
|
-
| `agent-kit outside/llm` |
|
|
30
|
+
| `agent-kit outside/llm` | Add/refresh a single registry item without touching the rest of the install (see `agent-kit list`) |
|
|
30
31
|
| `agent-kit update` | Re-install the current targets with the latest of everything |
|
|
31
32
|
| `agent-kit doctor` | Check tools, secrets, model endpoint, MCP servers, skills |
|
|
32
33
|
| `agent-kit secrets` | Enter missing secrets |
|
package/package.json
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
"baseURL": "${OUTSIDE_LLM_BASE_URL}",
|
|
6
6
|
"apiKey": "${OUTSIDE_LLM_API_KEY}",
|
|
7
7
|
"models": {
|
|
8
|
-
"nvidia/
|
|
9
|
-
"name": "
|
|
8
|
+
"nvidia/Qwen3.8-Flash-Next-NVFP4": {
|
|
9
|
+
"name": "Qwen3.8 Flash Next (NVFP4)",
|
|
10
10
|
"reasoning": true,
|
|
11
11
|
"contextWindow": 131072,
|
|
12
12
|
"maxTokens": 32768,
|
|
@@ -22,5 +22,5 @@
|
|
|
22
22
|
"OUTSIDE_LLM_BASE_URL",
|
|
23
23
|
"OUTSIDE_LLM_API_KEY"
|
|
24
24
|
],
|
|
25
|
-
"defaultModel": "nvidia/
|
|
25
|
+
"defaultModel": "nvidia/Qwen3.8-Flash-Next-NVFP4"
|
|
26
26
|
}
|
|
@@ -12,12 +12,13 @@ Write commits in Conventional Commits format:
|
|
|
12
12
|
|
|
13
13
|
<optional body: what changed and why, wrapped at 72>
|
|
14
14
|
|
|
15
|
-
Refs:
|
|
15
|
+
Refs: TICKET-123 (optional - omit the line when there is no ticket)
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
- `type` is one of: feat, fix, refactor, perf, test, docs, chore, ci.
|
|
19
19
|
- `scope` is the package, app or module name.
|
|
20
|
-
-
|
|
20
|
+
- The ticket reference is optional: include `Refs:` when a ticket exists
|
|
21
|
+
(e.g. the branch name contains one); omit the line entirely otherwise.
|
|
21
22
|
- One logical change per commit. Split unrelated changes.
|
|
22
23
|
- Always commit as the repository author, using the configured git
|
|
23
24
|
`user.name` and `user.email`. Never override the author.
|
package/src/adapters/omp.ts
CHANGED
|
@@ -76,12 +76,15 @@ export const omp: Adapter = {
|
|
|
76
76
|
|
|
77
77
|
async apply(plan, prev): Promise<ToolState> {
|
|
78
78
|
const state: ToolState = { skills: [], mcp: [], plugins: [], providers: [] };
|
|
79
|
+
// Additive installs (single registry item) only replace the keys this plan writes.
|
|
80
|
+
const keeps = plan.additive === true;
|
|
81
|
+
const mcpKeys = new Set(plan.items.filter((i) => i.kind === "mcp").map((i) => toolKey(i.id)));
|
|
79
82
|
|
|
80
83
|
// MCP servers
|
|
81
84
|
const { data: cfg } = await readJsonFile<Record<string, any>>(paths.ompMcpFile);
|
|
82
85
|
cfg.$schema ??= MCP_SCHEMA;
|
|
83
86
|
cfg.mcpServers ??= {};
|
|
84
|
-
for (const name of prev?.mcp ?? []) delete cfg.mcpServers[name];
|
|
87
|
+
for (const name of prev?.mcp ?? []) if (!keeps || mcpKeys.has(name)) delete cfg.mcpServers[name];
|
|
85
88
|
|
|
86
89
|
const skills = [...plan.skills];
|
|
87
90
|
const providers: Provider[] = [];
|
|
@@ -112,10 +115,13 @@ export const omp: Adapter = {
|
|
|
112
115
|
}
|
|
113
116
|
await writeJsonFile(paths.ompMcpFile, cfg);
|
|
114
117
|
|
|
115
|
-
if (providers.length || prev?.providers?.length) {
|
|
116
|
-
|
|
118
|
+
if (providers.length || (!keeps && prev?.providers?.length)) {
|
|
119
|
+
const dropProviders = (prev?.providers ?? []).filter(
|
|
120
|
+
(id) => !keeps || providers.some((p) => toolKey(p.id) === id),
|
|
121
|
+
);
|
|
122
|
+
state.providers = await writeProviders(providers, dropProviders);
|
|
117
123
|
}
|
|
118
|
-
state.skills = syncSkillsDir(paths.ompSkillsDir, skills, prev?.skills ?? [], "omp");
|
|
124
|
+
state.skills = syncSkillsDir(paths.ompSkillsDir, skills, prev?.skills ?? [], "omp", keeps);
|
|
119
125
|
|
|
120
126
|
log.ok(
|
|
121
127
|
`omp: ${state.skills.length} skills, ${state.mcp.length} MCP servers, ` +
|
package/src/adapters/opencode.ts
CHANGED
|
@@ -64,13 +64,24 @@ export const opencode: Adapter = {
|
|
|
64
64
|
cfg.$schema ??= "https://opencode.ai/config.json";
|
|
65
65
|
|
|
66
66
|
const state: ToolState = { skills: [], mcp: [], plugins: [], providers: [] };
|
|
67
|
+
// Additive installs (single registry item) only replace the keys this plan writes.
|
|
68
|
+
const keeps = plan.additive === true;
|
|
69
|
+
const dropIfReplaced = (managed: string[], keys: Set<string>) =>
|
|
70
|
+
managed.filter((k) => !keeps || keys.has(k));
|
|
71
|
+
const mcpKeys = new Set(plan.items.filter((i) => i.kind === "mcp").map((i) => toolKey(i.id)));
|
|
72
|
+
const providerKeys = new Set(
|
|
73
|
+
plan.items.filter((i) => i.kind === "provider").map((i) => toolKey(i.id)),
|
|
74
|
+
);
|
|
75
|
+
const pluginKeys = new Set<string>();
|
|
76
|
+
for (const i of plan.items) if (i.kind === "plugin" && i.opencode) pluginKeys.add(i.opencode.plugin);
|
|
67
77
|
|
|
68
|
-
// Drop
|
|
78
|
+
// Drop the managed entries this plan replaces (in non-additive mode: all of them).
|
|
69
79
|
cfg.mcp ??= {};
|
|
70
|
-
for (const name of prev?.mcp ?? []) delete cfg.mcp[name];
|
|
71
|
-
|
|
80
|
+
for (const name of dropIfReplaced(prev?.mcp ?? [], mcpKeys)) delete cfg.mcp[name];
|
|
81
|
+
const dropPlugins = dropIfReplaced(prev?.plugins ?? [], pluginKeys);
|
|
82
|
+
cfg.plugin = (cfg.plugin ?? []).filter((p: string) => !dropPlugins.includes(p));
|
|
72
83
|
cfg.provider ??= {};
|
|
73
|
-
for (const id of prev?.providers ?? []) delete cfg.provider[id];
|
|
84
|
+
for (const id of dropIfReplaced(prev?.providers ?? [], providerKeys)) delete cfg.provider[id];
|
|
74
85
|
|
|
75
86
|
const skills = [...plan.skills];
|
|
76
87
|
for (const item of plan.items) {
|
|
@@ -101,7 +112,7 @@ export const opencode: Adapter = {
|
|
|
101
112
|
if (!Object.keys(cfg.provider).length) delete cfg.provider;
|
|
102
113
|
|
|
103
114
|
await writeJsonFile(file, cfg);
|
|
104
|
-
state.skills = syncSkillsDir(paths.opencodeSkillsDir, skills, prev?.skills ?? [], "opencode");
|
|
115
|
+
state.skills = syncSkillsDir(paths.opencodeSkillsDir, skills, prev?.skills ?? [], "opencode", keeps);
|
|
105
116
|
|
|
106
117
|
log.ok(
|
|
107
118
|
`opencode: ${state.skills.length} skills, ${state.mcp.length} MCP servers, ` +
|
package/src/commands/install.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { loadItem, resolveProfiles, resolveTargets, secretsOf } from "../registr
|
|
|
5
5
|
import { resolveSkillSource } from "../sources";
|
|
6
6
|
import { ensureSecrets, ensureShellSourcesEnv, readEnvFile } from "../secrets";
|
|
7
7
|
import { readState, writeState } from "../state";
|
|
8
|
-
import type { Plan, PluginItem, State } from "../types";
|
|
8
|
+
import type { Plan, PluginItem, State, ToolState } from "../types";
|
|
9
9
|
|
|
10
10
|
export interface InstallOptions {
|
|
11
11
|
tools?: string[];
|
|
@@ -31,9 +31,24 @@ function pickTools(requested?: string[]): string[] {
|
|
|
31
31
|
return found;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/** Additive installs keep what the previous install managed; new keys win. */
|
|
35
|
+
function mergeToolState(prev: ToolState | undefined, next: ToolState): ToolState {
|
|
36
|
+
if (!prev) return next;
|
|
37
|
+
const u = (a: string[], b: string[]) => [...new Set([...a, ...b])];
|
|
38
|
+
return {
|
|
39
|
+
skills: u(prev.skills, next.skills),
|
|
40
|
+
mcp: u(prev.mcp, next.mcp),
|
|
41
|
+
plugins: u(prev.plugins, next.plugins),
|
|
42
|
+
providers: u(prev.providers, next.providers),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
34
46
|
export async function install(targets: string[], opts: InstallOptions = {}) {
|
|
35
47
|
const tools = pickTools(opts.tools);
|
|
36
48
|
const { profiles, items: direct } = await resolveTargets(targets);
|
|
49
|
+
// Targeting only registry items adds/replaces those items; it never purges
|
|
50
|
+
// the rest of the install. A profile target still reconciles the full set.
|
|
51
|
+
const additive = profiles.length === 0 && direct.length > 0;
|
|
37
52
|
log.head(`Setting up ${targets.map((t) => `"${t}"`).join(" + ")} for ${tools.join(" + ")}`);
|
|
38
53
|
|
|
39
54
|
const ids = [...new Set([...(await resolveProfiles(profiles)), ...direct])];
|
|
@@ -48,7 +63,7 @@ export async function install(targets: string[], opts: InstallOptions = {}) {
|
|
|
48
63
|
|
|
49
64
|
// Fetch skills (always latest).
|
|
50
65
|
log.step("Fetching skills");
|
|
51
|
-
const plan: Plan = { profiles, items, skills: [], fallbackSkills: {}, fetched: {} };
|
|
66
|
+
const plan: Plan = { profiles, items, skills: [], fallbackSkills: {}, fetched: {}, additive };
|
|
52
67
|
for (const item of items) {
|
|
53
68
|
try {
|
|
54
69
|
if (item.kind === "skills") {
|
|
@@ -79,15 +94,17 @@ export async function install(targets: string[], opts: InstallOptions = {}) {
|
|
|
79
94
|
log.step("Writing tool configs");
|
|
80
95
|
const prev = await readState();
|
|
81
96
|
const state: State = {
|
|
82
|
-
profiles,
|
|
83
|
-
items: direct,
|
|
97
|
+
profiles: additive ? [...new Set([...(prev?.profiles ?? []), ...profiles])] : profiles,
|
|
98
|
+
items: additive ? [...new Set([...(prev?.items ?? []), ...direct])] : direct,
|
|
84
99
|
tools: [...new Set([...(prev?.tools ?? []), ...tools])],
|
|
85
100
|
updatedAt: new Date().toISOString(),
|
|
86
101
|
managed: { ...(prev?.managed ?? {}) },
|
|
87
|
-
installed: plan.fetched,
|
|
102
|
+
installed: additive ? { ...(prev?.installed ?? {}), ...plan.fetched } : plan.fetched,
|
|
88
103
|
};
|
|
104
|
+
if (additive && prev) log.step(`Additive: keeping ${prev.profiles.join(", ") || "existing items"}; replacing ${direct.join(", ")}`);
|
|
89
105
|
for (const t of tools) {
|
|
90
|
-
|
|
106
|
+
const next = await adapters[t].apply(plan, prev?.managed[t]);
|
|
107
|
+
state.managed[t] = additive ? mergeToolState(prev?.managed[t], next) : next;
|
|
91
108
|
}
|
|
92
109
|
await writeState(state);
|
|
93
110
|
|
package/src/skills-install.ts
CHANGED
|
@@ -7,12 +7,15 @@ import type { ResolvedSkill } from "./types";
|
|
|
7
7
|
* Copy skills into a tool's skills dir. Only overwrites folders the kit
|
|
8
8
|
* installed before (tracked in `prevManaged`); a folder the user created
|
|
9
9
|
* with the same name is left alone. Removes kit skills no longer wanted.
|
|
10
|
+
* In `additive` mode (single registry-item installs) nothing is removed;
|
|
11
|
+
* managed folders in the plan are still refreshed.
|
|
10
12
|
*/
|
|
11
13
|
export function syncSkillsDir(
|
|
12
14
|
targetDir: string,
|
|
13
15
|
skills: ResolvedSkill[],
|
|
14
16
|
prevManaged: string[],
|
|
15
17
|
toolName: string,
|
|
18
|
+
additive = false,
|
|
16
19
|
): string[] {
|
|
17
20
|
mkdirSync(targetDir, { recursive: true });
|
|
18
21
|
const installed: string[] = [];
|
|
@@ -34,8 +37,9 @@ export function syncSkillsDir(
|
|
|
34
37
|
installed.push(skill.name);
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
if (!additive)
|
|
41
|
+
for (const old of prevManaged) {
|
|
42
|
+
if (!seen.has(old)) rmSync(join(targetDir, old), { recursive: true, force: true });
|
|
43
|
+
}
|
|
40
44
|
return installed;
|
|
41
45
|
}
|
package/src/types.ts
CHANGED
|
@@ -94,6 +94,11 @@ export interface Plan {
|
|
|
94
94
|
fallbackSkills: Record<string, ResolvedSkill[]>;
|
|
95
95
|
/** What was fetched, for the install log. */
|
|
96
96
|
fetched: Record<string, string>;
|
|
97
|
+
/**
|
|
98
|
+
* Single registry-item install: replace only the items in this plan and
|
|
99
|
+
* keep everything else the kit managed before (no purge of skills/MCP/etc).
|
|
100
|
+
*/
|
|
101
|
+
additive?: boolean;
|
|
97
102
|
}
|
|
98
103
|
|
|
99
104
|
export interface ToolState {
|