opencode-akane 0.1.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/LICENSE +21 -0
- package/README.md +95 -0
- package/dist/akane.d.ts +1 -0
- package/dist/akane.js +1 -0
- package/dist/artifacts.d.ts +38 -0
- package/dist/artifacts.js +144 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.js +132 -0
- package/dist/constants.d.ts +26 -0
- package/dist/constants.js +42 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/plugin.d.ts +3 -0
- package/dist/plugin.js +20 -0
- package/dist/tools/akane-init.d.ts +12 -0
- package/dist/tools/akane-init.js +53 -0
- package/dist/tools/akane-stage-artifact.d.ts +26 -0
- package/dist/tools/akane-stage-artifact.js +55 -0
- package/dist/types.d.ts +46 -0
- package/dist/types.js +1 -0
- package/examples/akane.example.json +38 -0
- package/package.json +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NaturaAurum
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Akane Agents
|
|
2
|
+
|
|
3
|
+
Akane is a global OpenCode orchestration plugin draft.
|
|
4
|
+
|
|
5
|
+
Current direction:
|
|
6
|
+
|
|
7
|
+
- Keep OpenCode provider and auth infrastructure
|
|
8
|
+
- Keep `oh-my-opencode` installed but neutralized
|
|
9
|
+
- Drive a deterministic workflow across planning, review, implementation, and synthesis
|
|
10
|
+
|
|
11
|
+
See [docs/session-context-20260306.md](/Users/taewoo.kim/Desktop/Projects/akane-agents/docs/session-context-20260306.md) for the current working context.
|
|
12
|
+
|
|
13
|
+
## MVP scaffold
|
|
14
|
+
|
|
15
|
+
This repository now contains the first MVP skeleton for the plugin:
|
|
16
|
+
|
|
17
|
+
- `src/plugin.ts`: OpenCode plugin entry
|
|
18
|
+
- `src/config.ts`: `~/.config/opencode/akane.json` loader with defaults
|
|
19
|
+
- `src/artifacts.ts`: `.opencode/akane/` artifact and `state.json` helpers
|
|
20
|
+
- `src/tools/akane-init.ts`: initializes the per-project workspace
|
|
21
|
+
- `src/tools/akane-stage-artifact.ts`: writes stage artifacts deterministically
|
|
22
|
+
- `examples/akane.example.json`: example global Akane config
|
|
23
|
+
|
|
24
|
+
## Local development
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
bun install
|
|
28
|
+
bun run typecheck
|
|
29
|
+
bun run build
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The build emits `dist/index.js` as the package entrypoint and also keeps `dist/akane.js` for local file-based linking.
|
|
33
|
+
|
|
34
|
+
## Package install
|
|
35
|
+
|
|
36
|
+
For package-based installation, publish this repository to npm and add it to the OpenCode plugin array in `~/.config/opencode/opencode.json`:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"$schema": "https://opencode.ai/config.json",
|
|
41
|
+
"plugin": [
|
|
42
|
+
"oh-my-opencode@latest",
|
|
43
|
+
"opencode-akane@0.1.0"
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Akane still reads its runtime config from `~/.config/opencode/akane.json`.
|
|
49
|
+
|
|
50
|
+
## Publish flow
|
|
51
|
+
|
|
52
|
+
The package is prepared to publish from npm with:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
bun install
|
|
56
|
+
bun run typecheck
|
|
57
|
+
bun run build
|
|
58
|
+
npm publish
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Useful checks before publishing:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
bun run pack:check
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Notes:
|
|
68
|
+
|
|
69
|
+
- The current package name assumption is `opencode-akane`
|
|
70
|
+
- The current license is `MIT`
|
|
71
|
+
- If you later switch to a scoped package name, publish with `npm publish --access public`
|
|
72
|
+
|
|
73
|
+
## Automated publishing
|
|
74
|
+
|
|
75
|
+
This repository includes GitHub Actions workflows for CI and npm publishing:
|
|
76
|
+
|
|
77
|
+
- `.github/workflows/ci.yml`: runs install, typecheck, build, and `npm pack --dry-run`
|
|
78
|
+
- `.github/workflows/publish.yml`: publishes to npm when a tag like `v0.1.0` is pushed
|
|
79
|
+
|
|
80
|
+
Recommended setup for public release:
|
|
81
|
+
|
|
82
|
+
1. Make the GitHub repository public
|
|
83
|
+
2. Publish `opencode-akane` once manually from your npm account, or reserve the package name
|
|
84
|
+
3. In npm package settings, add a Trusted Publisher for:
|
|
85
|
+
- GitHub owner: `NaturaAurum`
|
|
86
|
+
- Repository: `akane-agents`
|
|
87
|
+
- Workflow filename: `publish.yml`
|
|
88
|
+
4. Push a version tag that matches `package.json`, for example:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
git tag v0.1.0
|
|
92
|
+
git push origin main --tags
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The publish workflow intentionally uses npm trusted publishing with GitHub OIDC instead of an `NPM_TOKEN`.
|
package/dist/akane.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AkanePlugin as default, AkanePlugin } from "./plugin.js";
|
package/dist/akane.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AkanePlugin as default, AkanePlugin } from "./plugin.js";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AkaneConfig, AkaneStageId, AkaneState, ArtifactWriteMode } from "./types.js";
|
|
2
|
+
export declare function resolveProjectRoot(input: {
|
|
3
|
+
directory: string;
|
|
4
|
+
worktree?: string;
|
|
5
|
+
projectRoot?: string;
|
|
6
|
+
}): string;
|
|
7
|
+
export declare function resolveArtifactDir(projectRoot: string, config: AkaneConfig): string;
|
|
8
|
+
export declare function resolveStatePath(projectRoot: string, config: AkaneConfig): string;
|
|
9
|
+
export declare function resolveStageArtifactPath(projectRoot: string, config: AkaneConfig, stage: AkaneStageId): string;
|
|
10
|
+
export declare function createInitialState(input: {
|
|
11
|
+
config: AkaneConfig;
|
|
12
|
+
configPath: string;
|
|
13
|
+
projectRoot: string;
|
|
14
|
+
}): AkaneState;
|
|
15
|
+
export declare function ensureArtifactLayout(input: {
|
|
16
|
+
projectRoot: string;
|
|
17
|
+
config: AkaneConfig;
|
|
18
|
+
configPath: string;
|
|
19
|
+
force?: boolean;
|
|
20
|
+
}): Promise<{
|
|
21
|
+
artifactDir: string;
|
|
22
|
+
statePath: string;
|
|
23
|
+
state: AkaneState;
|
|
24
|
+
createdFiles: string[];
|
|
25
|
+
}>;
|
|
26
|
+
export declare function writeStageArtifact(input: {
|
|
27
|
+
projectRoot: string;
|
|
28
|
+
config: AkaneConfig;
|
|
29
|
+
configPath: string;
|
|
30
|
+
stage: AkaneStageId;
|
|
31
|
+
content: string;
|
|
32
|
+
mode: ArtifactWriteMode;
|
|
33
|
+
}): Promise<{
|
|
34
|
+
artifactDir: string;
|
|
35
|
+
artifactPath: string;
|
|
36
|
+
statePath: string;
|
|
37
|
+
state: AkaneState;
|
|
38
|
+
}>;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { AKANE_SERVICE_NAME, AKANE_STAGE_IDS, } from "./constants.js";
|
|
4
|
+
function nowIso() {
|
|
5
|
+
return new Date().toISOString();
|
|
6
|
+
}
|
|
7
|
+
function stageTitle(stage) {
|
|
8
|
+
return stage
|
|
9
|
+
.split("-")
|
|
10
|
+
.map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
|
|
11
|
+
.join(" ");
|
|
12
|
+
}
|
|
13
|
+
function defaultStageTemplate(stage) {
|
|
14
|
+
return `# ${stageTitle(stage)}\n\nGenerated by ${AKANE_SERVICE_NAME}.\n`;
|
|
15
|
+
}
|
|
16
|
+
export function resolveProjectRoot(input) {
|
|
17
|
+
if (input.projectRoot && input.projectRoot.trim()) {
|
|
18
|
+
return path.resolve(input.projectRoot);
|
|
19
|
+
}
|
|
20
|
+
if (input.worktree && input.worktree.trim()) {
|
|
21
|
+
return path.resolve(input.worktree);
|
|
22
|
+
}
|
|
23
|
+
return path.resolve(input.directory);
|
|
24
|
+
}
|
|
25
|
+
export function resolveArtifactDir(projectRoot, config) {
|
|
26
|
+
if (path.isAbsolute(config.artifacts.dir)) {
|
|
27
|
+
return config.artifacts.dir;
|
|
28
|
+
}
|
|
29
|
+
return path.join(projectRoot, config.artifacts.dir);
|
|
30
|
+
}
|
|
31
|
+
export function resolveStatePath(projectRoot, config) {
|
|
32
|
+
return path.join(resolveArtifactDir(projectRoot, config), config.artifacts.stateFile);
|
|
33
|
+
}
|
|
34
|
+
export function resolveStageArtifactPath(projectRoot, config, stage) {
|
|
35
|
+
return path.join(resolveArtifactDir(projectRoot, config), config.artifacts.files[stage]);
|
|
36
|
+
}
|
|
37
|
+
export function createInitialState(input) {
|
|
38
|
+
const artifactDir = resolveArtifactDir(input.projectRoot, input.config);
|
|
39
|
+
const timestamp = nowIso();
|
|
40
|
+
const stages = Object.fromEntries(AKANE_STAGE_IDS.map((stage) => [
|
|
41
|
+
stage,
|
|
42
|
+
{
|
|
43
|
+
path: resolveStageArtifactPath(input.projectRoot, input.config, stage),
|
|
44
|
+
status: "pending",
|
|
45
|
+
updatedAt: null,
|
|
46
|
+
},
|
|
47
|
+
]));
|
|
48
|
+
return {
|
|
49
|
+
version: input.config.version,
|
|
50
|
+
serviceName: input.config.serviceName,
|
|
51
|
+
configPath: input.configPath,
|
|
52
|
+
projectRoot: input.projectRoot,
|
|
53
|
+
artifactDir,
|
|
54
|
+
initializedAt: timestamp,
|
|
55
|
+
updatedAt: timestamp,
|
|
56
|
+
activeStage: null,
|
|
57
|
+
stageOrder: [...input.config.workflow.stageOrder],
|
|
58
|
+
stages,
|
|
59
|
+
roles: { ...input.config.roles },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
async function readStateFile(statePath) {
|
|
63
|
+
try {
|
|
64
|
+
const raw = await readFile(statePath, "utf8");
|
|
65
|
+
return JSON.parse(raw);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (error.code === "ENOENT") {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async function writeStateFile(statePath, state) {
|
|
75
|
+
await writeFile(statePath, JSON.stringify(state, null, 2) + "\n", "utf8");
|
|
76
|
+
}
|
|
77
|
+
export async function ensureArtifactLayout(input) {
|
|
78
|
+
const artifactDir = resolveArtifactDir(input.projectRoot, input.config);
|
|
79
|
+
const statePath = resolveStatePath(input.projectRoot, input.config);
|
|
80
|
+
const createdFiles = [];
|
|
81
|
+
await mkdir(artifactDir, { recursive: true });
|
|
82
|
+
for (const stage of AKANE_STAGE_IDS) {
|
|
83
|
+
const artifactPath = resolveStageArtifactPath(input.projectRoot, input.config, stage);
|
|
84
|
+
try {
|
|
85
|
+
if (input.force) {
|
|
86
|
+
throw Object.assign(new Error("force"), { code: "ENOENT" });
|
|
87
|
+
}
|
|
88
|
+
await readFile(artifactPath, "utf8");
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
if (error.code !== "ENOENT") {
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
await writeFile(artifactPath, defaultStageTemplate(stage), "utf8");
|
|
95
|
+
createdFiles.push(artifactPath);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
let state = await readStateFile(statePath);
|
|
99
|
+
if (!state || input.force) {
|
|
100
|
+
state = createInitialState({
|
|
101
|
+
config: input.config,
|
|
102
|
+
configPath: input.configPath,
|
|
103
|
+
projectRoot: input.projectRoot,
|
|
104
|
+
});
|
|
105
|
+
for (const stage of AKANE_STAGE_IDS) {
|
|
106
|
+
state.stages[stage].status = "initialized";
|
|
107
|
+
state.stages[stage].updatedAt = state.initializedAt;
|
|
108
|
+
}
|
|
109
|
+
await writeStateFile(statePath, state);
|
|
110
|
+
createdFiles.push(statePath);
|
|
111
|
+
}
|
|
112
|
+
return { artifactDir, statePath, state, createdFiles };
|
|
113
|
+
}
|
|
114
|
+
export async function writeStageArtifact(input) {
|
|
115
|
+
const ensured = await ensureArtifactLayout({
|
|
116
|
+
projectRoot: input.projectRoot,
|
|
117
|
+
config: input.config,
|
|
118
|
+
configPath: input.configPath,
|
|
119
|
+
});
|
|
120
|
+
const artifactPath = resolveStageArtifactPath(input.projectRoot, input.config, input.stage);
|
|
121
|
+
const previous = input.mode === "append"
|
|
122
|
+
? await readFile(artifactPath, "utf8").catch((error) => error.code === "ENOENT" ? "" : Promise.reject(error))
|
|
123
|
+
: "";
|
|
124
|
+
const nextContent = input.mode === "append"
|
|
125
|
+
? `${previous}${previous.endsWith("\n") || !previous ? "" : "\n"}${input.content}\n`
|
|
126
|
+
: `${input.content}${input.content.endsWith("\n") ? "" : "\n"}`;
|
|
127
|
+
await writeFile(artifactPath, nextContent, "utf8");
|
|
128
|
+
const state = ensured.state;
|
|
129
|
+
const timestamp = nowIso();
|
|
130
|
+
state.activeStage = input.stage;
|
|
131
|
+
state.updatedAt = timestamp;
|
|
132
|
+
state.stages[input.stage] = {
|
|
133
|
+
path: artifactPath,
|
|
134
|
+
status: "completed",
|
|
135
|
+
updatedAt: timestamp,
|
|
136
|
+
};
|
|
137
|
+
await writeStateFile(ensured.statePath, state);
|
|
138
|
+
return {
|
|
139
|
+
artifactDir: ensured.artifactDir,
|
|
140
|
+
artifactPath,
|
|
141
|
+
statePath: ensured.statePath,
|
|
142
|
+
state,
|
|
143
|
+
};
|
|
144
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AkaneConfig, LoadedAkaneConfig } from "./types.js";
|
|
2
|
+
type DeepPartial<T> = {
|
|
3
|
+
[K in keyof T]?: T[K] extends Array<infer U> ? U[] : T[K] extends Record<string, unknown> ? DeepPartial<T[K]> : T[K];
|
|
4
|
+
};
|
|
5
|
+
export declare function expandHome(inputPath: string): string;
|
|
6
|
+
export declare function defaultAkaneConfig(): AkaneConfig;
|
|
7
|
+
export declare function mergeAkaneConfig(base: AkaneConfig, overrides: DeepPartial<AkaneConfig>): AkaneConfig;
|
|
8
|
+
export declare function loadAkaneConfig(configPath?: string): Promise<LoadedAkaneConfig>;
|
|
9
|
+
export {};
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { AKANE_SERVICE_NAME, AKANE_STAGE_IDS, DEFAULT_ARTIFACT_DIR, DEFAULT_GLOBAL_CONFIG_PATH, DEFAULT_PLUGIN_OUTPUT_PATH, DEFAULT_ROLE_MODELS, DEFAULT_STAGE_FILES, DEFAULT_STAGE_ORDER, DEFAULT_STATE_FILE, } from "./constants.js";
|
|
5
|
+
function isRecord(value) {
|
|
6
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
export function expandHome(inputPath) {
|
|
9
|
+
if (inputPath === "~") {
|
|
10
|
+
return os.homedir();
|
|
11
|
+
}
|
|
12
|
+
if (inputPath.startsWith("~/")) {
|
|
13
|
+
return path.join(os.homedir(), inputPath.slice(2));
|
|
14
|
+
}
|
|
15
|
+
return inputPath;
|
|
16
|
+
}
|
|
17
|
+
export function defaultAkaneConfig() {
|
|
18
|
+
return {
|
|
19
|
+
version: 1,
|
|
20
|
+
serviceName: AKANE_SERVICE_NAME,
|
|
21
|
+
pluginOutputPath: DEFAULT_PLUGIN_OUTPUT_PATH,
|
|
22
|
+
globalConfigPath: DEFAULT_GLOBAL_CONFIG_PATH,
|
|
23
|
+
artifacts: {
|
|
24
|
+
dir: DEFAULT_ARTIFACT_DIR,
|
|
25
|
+
stateFile: DEFAULT_STATE_FILE,
|
|
26
|
+
files: { ...DEFAULT_STAGE_FILES },
|
|
27
|
+
},
|
|
28
|
+
workflow: {
|
|
29
|
+
stageOrder: [...DEFAULT_STAGE_ORDER],
|
|
30
|
+
},
|
|
31
|
+
roles: { ...DEFAULT_ROLE_MODELS },
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function parseJsonFile(content, filePath) {
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(content);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
const message = error instanceof Error ? error.message : "unknown JSON parse error";
|
|
40
|
+
throw new Error(`Failed to parse Akane config at ${filePath}: ${message}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function normalizeRoles(input, fallback) {
|
|
44
|
+
if (!isRecord(input)) {
|
|
45
|
+
return { ...fallback };
|
|
46
|
+
}
|
|
47
|
+
const next = { ...fallback };
|
|
48
|
+
for (const role of Object.keys(fallback)) {
|
|
49
|
+
const candidate = input[role];
|
|
50
|
+
if (typeof candidate === "string" && candidate.trim()) {
|
|
51
|
+
next[role] = candidate.trim();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return next;
|
|
55
|
+
}
|
|
56
|
+
function normalizeFiles(input, fallback) {
|
|
57
|
+
if (!isRecord(input)) {
|
|
58
|
+
return { ...fallback };
|
|
59
|
+
}
|
|
60
|
+
const next = { ...fallback };
|
|
61
|
+
for (const stage of AKANE_STAGE_IDS) {
|
|
62
|
+
const candidate = input[stage];
|
|
63
|
+
if (typeof candidate === "string" && candidate.trim()) {
|
|
64
|
+
next[stage] = candidate.trim();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return next;
|
|
68
|
+
}
|
|
69
|
+
function normalizeStageOrder(input, fallback) {
|
|
70
|
+
if (!Array.isArray(input)) {
|
|
71
|
+
return [...fallback];
|
|
72
|
+
}
|
|
73
|
+
const values = input.filter((value) => typeof value === "string" &&
|
|
74
|
+
AKANE_STAGE_IDS.includes(value));
|
|
75
|
+
return values.length === AKANE_STAGE_IDS.length ? values : [...fallback];
|
|
76
|
+
}
|
|
77
|
+
export function mergeAkaneConfig(base, overrides) {
|
|
78
|
+
return {
|
|
79
|
+
version: typeof overrides.version === "number" ? overrides.version : base.version,
|
|
80
|
+
serviceName: typeof overrides.serviceName === "string" && overrides.serviceName.trim()
|
|
81
|
+
? overrides.serviceName.trim()
|
|
82
|
+
: base.serviceName,
|
|
83
|
+
pluginOutputPath: typeof overrides.pluginOutputPath === "string" &&
|
|
84
|
+
overrides.pluginOutputPath.trim()
|
|
85
|
+
? overrides.pluginOutputPath.trim()
|
|
86
|
+
: base.pluginOutputPath,
|
|
87
|
+
globalConfigPath: typeof overrides.globalConfigPath === "string" &&
|
|
88
|
+
overrides.globalConfigPath.trim()
|
|
89
|
+
? overrides.globalConfigPath.trim()
|
|
90
|
+
: base.globalConfigPath,
|
|
91
|
+
artifacts: {
|
|
92
|
+
dir: isRecord(overrides.artifacts) &&
|
|
93
|
+
typeof overrides.artifacts.dir === "string" &&
|
|
94
|
+
overrides.artifacts.dir.trim()
|
|
95
|
+
? overrides.artifacts.dir.trim()
|
|
96
|
+
: base.artifacts.dir,
|
|
97
|
+
stateFile: isRecord(overrides.artifacts) &&
|
|
98
|
+
typeof overrides.artifacts.stateFile === "string" &&
|
|
99
|
+
overrides.artifacts.stateFile.trim()
|
|
100
|
+
? overrides.artifacts.stateFile.trim()
|
|
101
|
+
: base.artifacts.stateFile,
|
|
102
|
+
files: normalizeFiles(isRecord(overrides.artifacts) ? overrides.artifacts.files : undefined, base.artifacts.files),
|
|
103
|
+
},
|
|
104
|
+
workflow: {
|
|
105
|
+
stageOrder: normalizeStageOrder(isRecord(overrides.workflow) ? overrides.workflow.stageOrder : undefined, base.workflow.stageOrder),
|
|
106
|
+
},
|
|
107
|
+
roles: normalizeRoles(overrides.roles, base.roles),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
export async function loadAkaneConfig(configPath = process.env.AKANE_CONFIG_PATH ?? DEFAULT_GLOBAL_CONFIG_PATH) {
|
|
111
|
+
const resolvedPath = expandHome(configPath);
|
|
112
|
+
const defaults = defaultAkaneConfig();
|
|
113
|
+
try {
|
|
114
|
+
const raw = await readFile(resolvedPath, "utf8");
|
|
115
|
+
const parsed = parseJsonFile(raw, resolvedPath);
|
|
116
|
+
return {
|
|
117
|
+
path: resolvedPath,
|
|
118
|
+
exists: true,
|
|
119
|
+
config: mergeAkaneConfig(defaults, parsed),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
if (error.code === "ENOENT") {
|
|
124
|
+
return {
|
|
125
|
+
path: resolvedPath,
|
|
126
|
+
exists: false,
|
|
127
|
+
config: defaults,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare const AKANE_SERVICE_NAME = "Akane";
|
|
2
|
+
export declare const DEFAULT_GLOBAL_CONFIG_PATH = "~/.config/opencode/akane.json";
|
|
3
|
+
export declare const DEFAULT_PLUGIN_OUTPUT_PATH = "~/.config/opencode/plugins/akane.js";
|
|
4
|
+
export declare const DEFAULT_ARTIFACT_DIR = ".opencode/akane";
|
|
5
|
+
export declare const DEFAULT_STATE_FILE = "state.json";
|
|
6
|
+
export declare const AKANE_STAGE_IDS: readonly ["plan", "plan-review", "implementation-context", "review-codex", "review-claude", "final-synthesis"];
|
|
7
|
+
export declare const AKANE_ROLE_IDS: readonly ["planner", "plan_reviewer", "implementer", "consultant_primary", "consultant_secondary", "reviewer_codex", "reviewer_claude", "synthesizer"];
|
|
8
|
+
export declare const DEFAULT_ROLE_MODELS: {
|
|
9
|
+
readonly planner: "anthropic/claude-opus-4-6";
|
|
10
|
+
readonly plan_reviewer: "openai/gpt-5.4";
|
|
11
|
+
readonly implementer: "openai/gpt-5.4";
|
|
12
|
+
readonly consultant_primary: "anthropic/claude-opus-4-6";
|
|
13
|
+
readonly consultant_secondary: "anthropic/claude-sonnet-4-6";
|
|
14
|
+
readonly reviewer_codex: "openai/gpt-5.4";
|
|
15
|
+
readonly reviewer_claude: "anthropic/claude-opus-4-6";
|
|
16
|
+
readonly synthesizer: "openai/gpt-5.4";
|
|
17
|
+
};
|
|
18
|
+
export declare const DEFAULT_STAGE_FILES: {
|
|
19
|
+
readonly plan: "plan.md";
|
|
20
|
+
readonly "plan-review": "plan-review.md";
|
|
21
|
+
readonly "implementation-context": "implementation-context.md";
|
|
22
|
+
readonly "review-codex": "review-codex.md";
|
|
23
|
+
readonly "review-claude": "review-claude.md";
|
|
24
|
+
readonly "final-synthesis": "final-synthesis.md";
|
|
25
|
+
};
|
|
26
|
+
export declare const DEFAULT_STAGE_ORDER: ("plan" | "plan-review" | "implementation-context" | "review-codex" | "review-claude" | "final-synthesis")[];
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export const AKANE_SERVICE_NAME = "Akane";
|
|
2
|
+
export const DEFAULT_GLOBAL_CONFIG_PATH = "~/.config/opencode/akane.json";
|
|
3
|
+
export const DEFAULT_PLUGIN_OUTPUT_PATH = "~/.config/opencode/plugins/akane.js";
|
|
4
|
+
export const DEFAULT_ARTIFACT_DIR = ".opencode/akane";
|
|
5
|
+
export const DEFAULT_STATE_FILE = "state.json";
|
|
6
|
+
export const AKANE_STAGE_IDS = [
|
|
7
|
+
"plan",
|
|
8
|
+
"plan-review",
|
|
9
|
+
"implementation-context",
|
|
10
|
+
"review-codex",
|
|
11
|
+
"review-claude",
|
|
12
|
+
"final-synthesis",
|
|
13
|
+
];
|
|
14
|
+
export const AKANE_ROLE_IDS = [
|
|
15
|
+
"planner",
|
|
16
|
+
"plan_reviewer",
|
|
17
|
+
"implementer",
|
|
18
|
+
"consultant_primary",
|
|
19
|
+
"consultant_secondary",
|
|
20
|
+
"reviewer_codex",
|
|
21
|
+
"reviewer_claude",
|
|
22
|
+
"synthesizer",
|
|
23
|
+
];
|
|
24
|
+
export const DEFAULT_ROLE_MODELS = {
|
|
25
|
+
planner: "anthropic/claude-opus-4-6",
|
|
26
|
+
plan_reviewer: "openai/gpt-5.4",
|
|
27
|
+
implementer: "openai/gpt-5.4",
|
|
28
|
+
consultant_primary: "anthropic/claude-opus-4-6",
|
|
29
|
+
consultant_secondary: "anthropic/claude-sonnet-4-6",
|
|
30
|
+
reviewer_codex: "openai/gpt-5.4",
|
|
31
|
+
reviewer_claude: "anthropic/claude-opus-4-6",
|
|
32
|
+
synthesizer: "openai/gpt-5.4",
|
|
33
|
+
};
|
|
34
|
+
export const DEFAULT_STAGE_FILES = {
|
|
35
|
+
plan: "plan.md",
|
|
36
|
+
"plan-review": "plan-review.md",
|
|
37
|
+
"implementation-context": "implementation-context.md",
|
|
38
|
+
"review-codex": "review-codex.md",
|
|
39
|
+
"review-claude": "review-claude.md",
|
|
40
|
+
"final-synthesis": "final-synthesis.md",
|
|
41
|
+
};
|
|
42
|
+
export const DEFAULT_STAGE_ORDER = [...AKANE_STAGE_IDS];
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AkanePlugin as default, AkanePlugin } from "./plugin.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AkanePlugin as default, AkanePlugin } from "./plugin.js";
|
package/dist/plugin.d.ts
ADDED
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { loadAkaneConfig } from "./config.js";
|
|
2
|
+
import { resolveArtifactDir } from "./artifacts.js";
|
|
3
|
+
import { createAkaneInitTool } from "./tools/akane-init.js";
|
|
4
|
+
import { createAkaneStageArtifactTool } from "./tools/akane-stage-artifact.js";
|
|
5
|
+
export const AkanePlugin = async (input) => {
|
|
6
|
+
const configInfo = await loadAkaneConfig();
|
|
7
|
+
return {
|
|
8
|
+
tool: {
|
|
9
|
+
akane_init: createAkaneInitTool(configInfo),
|
|
10
|
+
akane_stage_artifact: createAkaneStageArtifactTool(configInfo),
|
|
11
|
+
},
|
|
12
|
+
"shell.env": async (_event, output) => {
|
|
13
|
+
const projectRoot = input.worktree || input.directory;
|
|
14
|
+
output.env.AKANE_PROJECT_ROOT = projectRoot;
|
|
15
|
+
output.env.AKANE_ARTIFACT_DIR = resolveArtifactDir(projectRoot, configInfo.config);
|
|
16
|
+
output.env.AKANE_CONFIG_PATH = configInfo.path;
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
export default AkanePlugin;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { LoadedAkaneConfig } from "../types.js";
|
|
2
|
+
export declare function createAkaneInitTool(configInfo: LoadedAkaneConfig): {
|
|
3
|
+
description: string;
|
|
4
|
+
args: {
|
|
5
|
+
force: import("zod").ZodDefault<import("zod").ZodBoolean>;
|
|
6
|
+
projectRoot: import("zod").ZodOptional<import("zod").ZodString>;
|
|
7
|
+
};
|
|
8
|
+
execute(args: {
|
|
9
|
+
force: boolean;
|
|
10
|
+
projectRoot?: string | undefined;
|
|
11
|
+
}, context: import("@opencode-ai/plugin/tool").ToolContext): Promise<string>;
|
|
12
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin/tool";
|
|
2
|
+
import { ensureArtifactLayout, resolveProjectRoot } from "../artifacts.js";
|
|
3
|
+
export function createAkaneInitTool(configInfo) {
|
|
4
|
+
return tool({
|
|
5
|
+
description: "Initialize the per-project .opencode/akane workspace and create the stage artifact files.",
|
|
6
|
+
args: {
|
|
7
|
+
force: tool.schema
|
|
8
|
+
.boolean()
|
|
9
|
+
.default(false)
|
|
10
|
+
.describe("Recreate artifact files and state.json even when they already exist."),
|
|
11
|
+
projectRoot: tool.schema
|
|
12
|
+
.string()
|
|
13
|
+
.optional()
|
|
14
|
+
.describe("Optional project root override. Defaults to the current session worktree."),
|
|
15
|
+
},
|
|
16
|
+
async execute(args, context) {
|
|
17
|
+
const projectRoot = resolveProjectRoot({
|
|
18
|
+
directory: context.directory,
|
|
19
|
+
worktree: context.worktree,
|
|
20
|
+
projectRoot: args.projectRoot,
|
|
21
|
+
});
|
|
22
|
+
const result = await ensureArtifactLayout({
|
|
23
|
+
projectRoot,
|
|
24
|
+
config: configInfo.config,
|
|
25
|
+
configPath: configInfo.path,
|
|
26
|
+
force: args.force,
|
|
27
|
+
});
|
|
28
|
+
context.metadata({
|
|
29
|
+
title: "Akane initialized",
|
|
30
|
+
metadata: {
|
|
31
|
+
projectRoot,
|
|
32
|
+
artifactDir: result.artifactDir,
|
|
33
|
+
configPath: configInfo.path,
|
|
34
|
+
configFound: configInfo.exists,
|
|
35
|
+
createdFiles: result.createdFiles,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
const lines = [
|
|
39
|
+
`Initialized ${configInfo.config.serviceName} in ${projectRoot}.`,
|
|
40
|
+
`Artifact directory: ${result.artifactDir}`,
|
|
41
|
+
`State file: ${result.statePath}`,
|
|
42
|
+
`Config path: ${configInfo.path}${configInfo.exists ? "" : " (default config fallback)"}`,
|
|
43
|
+
];
|
|
44
|
+
if (result.createdFiles.length > 0) {
|
|
45
|
+
lines.push(`Created files: ${result.createdFiles.length}`);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
lines.push("No files were recreated.");
|
|
49
|
+
}
|
|
50
|
+
return lines.join("\n");
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { LoadedAkaneConfig } from "../types.js";
|
|
2
|
+
export declare function createAkaneStageArtifactTool(configInfo: LoadedAkaneConfig): {
|
|
3
|
+
description: string;
|
|
4
|
+
args: {
|
|
5
|
+
stage: import("zod").ZodEnum<{
|
|
6
|
+
plan: "plan";
|
|
7
|
+
"plan-review": "plan-review";
|
|
8
|
+
"implementation-context": "implementation-context";
|
|
9
|
+
"review-codex": "review-codex";
|
|
10
|
+
"review-claude": "review-claude";
|
|
11
|
+
"final-synthesis": "final-synthesis";
|
|
12
|
+
}>;
|
|
13
|
+
content: import("zod").ZodString;
|
|
14
|
+
mode: import("zod").ZodDefault<import("zod").ZodEnum<{
|
|
15
|
+
append: "append";
|
|
16
|
+
replace: "replace";
|
|
17
|
+
}>>;
|
|
18
|
+
projectRoot: import("zod").ZodOptional<import("zod").ZodString>;
|
|
19
|
+
};
|
|
20
|
+
execute(args: {
|
|
21
|
+
stage: "plan" | "plan-review" | "implementation-context" | "review-codex" | "review-claude" | "final-synthesis";
|
|
22
|
+
content: string;
|
|
23
|
+
mode: "append" | "replace";
|
|
24
|
+
projectRoot?: string | undefined;
|
|
25
|
+
}, context: import("@opencode-ai/plugin/tool").ToolContext): Promise<string>;
|
|
26
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin/tool";
|
|
2
|
+
import { AKANE_STAGE_IDS } from "../constants.js";
|
|
3
|
+
import { resolveProjectRoot, writeStageArtifact } from "../artifacts.js";
|
|
4
|
+
export function createAkaneStageArtifactTool(configInfo) {
|
|
5
|
+
return tool({
|
|
6
|
+
description: "Write deterministic stage artifacts into .opencode/akane and update Akane state.json.",
|
|
7
|
+
args: {
|
|
8
|
+
stage: tool.schema
|
|
9
|
+
.enum(AKANE_STAGE_IDS)
|
|
10
|
+
.describe("The workflow stage whose artifact file should be written."),
|
|
11
|
+
content: tool.schema
|
|
12
|
+
.string()
|
|
13
|
+
.describe("Markdown content to write into the selected stage artifact."),
|
|
14
|
+
mode: tool.schema
|
|
15
|
+
.enum(["replace", "append"])
|
|
16
|
+
.default("replace")
|
|
17
|
+
.describe("Replace the artifact content or append to the existing file."),
|
|
18
|
+
projectRoot: tool.schema
|
|
19
|
+
.string()
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("Optional project root override. Defaults to the current session worktree."),
|
|
22
|
+
},
|
|
23
|
+
async execute(args, context) {
|
|
24
|
+
const projectRoot = resolveProjectRoot({
|
|
25
|
+
directory: context.directory,
|
|
26
|
+
worktree: context.worktree,
|
|
27
|
+
projectRoot: args.projectRoot,
|
|
28
|
+
});
|
|
29
|
+
const result = await writeStageArtifact({
|
|
30
|
+
projectRoot,
|
|
31
|
+
config: configInfo.config,
|
|
32
|
+
configPath: configInfo.path,
|
|
33
|
+
stage: args.stage,
|
|
34
|
+
content: args.content,
|
|
35
|
+
mode: args.mode,
|
|
36
|
+
});
|
|
37
|
+
context.metadata({
|
|
38
|
+
title: `Akane stage updated: ${args.stage}`,
|
|
39
|
+
metadata: {
|
|
40
|
+
stage: args.stage,
|
|
41
|
+
mode: args.mode,
|
|
42
|
+
artifactPath: result.artifactPath,
|
|
43
|
+
statePath: result.statePath,
|
|
44
|
+
projectRoot,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
return [
|
|
48
|
+
`Updated stage ${args.stage}.`,
|
|
49
|
+
`Artifact: ${result.artifactPath}`,
|
|
50
|
+
`State: ${result.statePath}`,
|
|
51
|
+
`Mode: ${args.mode}`,
|
|
52
|
+
].join("\n");
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { AKANE_ROLE_IDS, AKANE_STAGE_IDS, DEFAULT_ROLE_MODELS, DEFAULT_STAGE_FILES } from "./constants.js";
|
|
2
|
+
export type AkaneStageId = (typeof AKANE_STAGE_IDS)[number];
|
|
3
|
+
export type AkaneRoleId = (typeof AKANE_ROLE_IDS)[number];
|
|
4
|
+
export type AkaneRoles = Record<AkaneRoleId, string>;
|
|
5
|
+
export type AkaneStageFiles = Record<AkaneStageId, string>;
|
|
6
|
+
export interface AkaneConfig {
|
|
7
|
+
version: number;
|
|
8
|
+
serviceName: string;
|
|
9
|
+
pluginOutputPath: string;
|
|
10
|
+
globalConfigPath: string;
|
|
11
|
+
artifacts: {
|
|
12
|
+
dir: string;
|
|
13
|
+
stateFile: string;
|
|
14
|
+
files: AkaneStageFiles;
|
|
15
|
+
};
|
|
16
|
+
workflow: {
|
|
17
|
+
stageOrder: AkaneStageId[];
|
|
18
|
+
};
|
|
19
|
+
roles: AkaneRoles;
|
|
20
|
+
}
|
|
21
|
+
export interface LoadedAkaneConfig {
|
|
22
|
+
path: string;
|
|
23
|
+
exists: boolean;
|
|
24
|
+
config: AkaneConfig;
|
|
25
|
+
}
|
|
26
|
+
export interface AkaneStageState {
|
|
27
|
+
path: string;
|
|
28
|
+
status: "pending" | "initialized" | "completed";
|
|
29
|
+
updatedAt: string | null;
|
|
30
|
+
}
|
|
31
|
+
export interface AkaneState {
|
|
32
|
+
version: number;
|
|
33
|
+
serviceName: string;
|
|
34
|
+
configPath: string;
|
|
35
|
+
projectRoot: string;
|
|
36
|
+
artifactDir: string;
|
|
37
|
+
initializedAt: string;
|
|
38
|
+
updatedAt: string;
|
|
39
|
+
activeStage: AkaneStageId | null;
|
|
40
|
+
stageOrder: AkaneStageId[];
|
|
41
|
+
stages: Record<AkaneStageId, AkaneStageState>;
|
|
42
|
+
roles: AkaneRoles;
|
|
43
|
+
}
|
|
44
|
+
export type ArtifactWriteMode = "append" | "replace";
|
|
45
|
+
export type RoleModelDefaults = typeof DEFAULT_ROLE_MODELS;
|
|
46
|
+
export type StageFileDefaults = typeof DEFAULT_STAGE_FILES;
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"serviceName": "Akane",
|
|
4
|
+
"pluginOutputPath": "~/.config/opencode/plugins/akane.js",
|
|
5
|
+
"globalConfigPath": "~/.config/opencode/akane.json",
|
|
6
|
+
"artifacts": {
|
|
7
|
+
"dir": ".opencode/akane",
|
|
8
|
+
"stateFile": "state.json",
|
|
9
|
+
"files": {
|
|
10
|
+
"plan": "plan.md",
|
|
11
|
+
"plan-review": "plan-review.md",
|
|
12
|
+
"implementation-context": "implementation-context.md",
|
|
13
|
+
"review-codex": "review-codex.md",
|
|
14
|
+
"review-claude": "review-claude.md",
|
|
15
|
+
"final-synthesis": "final-synthesis.md"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"workflow": {
|
|
19
|
+
"stageOrder": [
|
|
20
|
+
"plan",
|
|
21
|
+
"plan-review",
|
|
22
|
+
"implementation-context",
|
|
23
|
+
"review-codex",
|
|
24
|
+
"review-claude",
|
|
25
|
+
"final-synthesis"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"roles": {
|
|
29
|
+
"planner": "anthropic/claude-opus-4-6",
|
|
30
|
+
"plan_reviewer": "openai/gpt-5.4",
|
|
31
|
+
"implementer": "openai/gpt-5.4",
|
|
32
|
+
"consultant_primary": "anthropic/claude-opus-4-6",
|
|
33
|
+
"consultant_secondary": "anthropic/claude-sonnet-4-6",
|
|
34
|
+
"reviewer_codex": "openai/gpt-5.4",
|
|
35
|
+
"reviewer_claude": "anthropic/claude-opus-4-6",
|
|
36
|
+
"synthesizer": "openai/gpt-5.4"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-akane",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Akane orchestration plugin for OpenCode",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"opencode",
|
|
9
|
+
"plugin",
|
|
10
|
+
"akane",
|
|
11
|
+
"automation"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://github.com/NaturaAurum/akane-agents#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/NaturaAurum/akane-agents/issues"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/NaturaAurum/akane-agents.git"
|
|
20
|
+
},
|
|
21
|
+
"main": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"import": "./dist/index.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"README.md",
|
|
32
|
+
"examples/akane.example.json"
|
|
33
|
+
],
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=22"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"registry": "https://registry.npmjs.org/"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"clean": "rm -rf dist",
|
|
42
|
+
"build": "tsc -p tsconfig.json",
|
|
43
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
44
|
+
"pack:check": "npm pack --dry-run",
|
|
45
|
+
"prepublishOnly": "bun run clean && bun run build && bun run typecheck"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@opencode-ai/plugin": "1.2.20"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "^24.12.0",
|
|
52
|
+
"typescript": "^5.9.3"
|
|
53
|
+
}
|
|
54
|
+
}
|