@opencode-compat/profile 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/README.md ADDED
@@ -0,0 +1,14 @@
1
+ # @opencode-compat/profile
2
+
3
+ HostProfile types, draft profiles (`opencode` / `mimo` / `kilo` / `zcode`), and `detect()`.
4
+
5
+ **License:** MPL-2.0
6
+
7
+ ```ts
8
+ import { detect, mimoProfile, CORE_HOOKS } from "@opencode-compat/profile"
9
+
10
+ const { profile, supported, message } = detect()
11
+ // or: detect({ env: { OPENCODE_COMPAT_HOST: "mimo" } })
12
+ ```
13
+
14
+ See the monorepo [README](../../README.md) and [OCP 0.1](../../docs/ocp/0.1.md) §5.
@@ -0,0 +1,13 @@
1
+ import type { DetectOptions, DetectResult } from "./types";
2
+ /**
3
+ * Detect the current OCP host.
4
+ *
5
+ * Order (docs/ocp/0.1.md §5.1):
6
+ * 1. `OPENCODE_COMPAT_HOST`
7
+ * 2. Native binary / package identity
8
+ * 3. Config-dir heuristics
9
+ * 4. `zcode` → refuse OCP load (T0 doctor)
10
+ * 5. `unknown` → fail with doctor
11
+ */
12
+ export declare function detect(options?: DetectOptions): DetectResult;
13
+ //# sourceMappingURL=detect.d.ts.map
package/dist/detect.js ADDED
@@ -0,0 +1,155 @@
1
+ import { existsSync as nodeExistsSync } from "node:fs";
2
+ import { basename } from "node:path";
3
+ import { DRAFTS, kiloProfile, mimoProfile, opencodeProfile, unknownProfile, zcodeProfile, } from "./drafts";
4
+ import { unknownDoctorMessage, zcodeDoctorMessage } from "./doctor";
5
+ const HOST_IDS = ["opencode", "mimo", "kilo", "zcode"];
6
+ function isHostId(value) {
7
+ return HOST_IDS.includes(value);
8
+ }
9
+ function profileFor(id, options) {
10
+ const draft = DRAFTS[id];
11
+ return draft({ env: options.env, home: options.home });
12
+ }
13
+ function result(id, profile, source, supported, message) {
14
+ return { id, profile, source, supported, message };
15
+ }
16
+ function binaryHint(options) {
17
+ const argv = options.argv ?? process.argv;
18
+ const execPath = options.execPath ?? process.execPath;
19
+ const tokens = [
20
+ ...argv.map((a) => basename(a).toLowerCase()),
21
+ basename(execPath).toLowerCase(),
22
+ ];
23
+ for (const token of tokens) {
24
+ if (token === "mimo" ||
25
+ token === "mimocode" ||
26
+ token.startsWith("mimo-") ||
27
+ token.includes("mimocode")) {
28
+ return "mimo";
29
+ }
30
+ if (token === "kilo" ||
31
+ token === "kilocode" ||
32
+ token.startsWith("kilo-") ||
33
+ token.includes("kilocode")) {
34
+ return "kilo";
35
+ }
36
+ if (token === "zcode" ||
37
+ token.startsWith("zcode-") ||
38
+ token.includes("zcode")) {
39
+ return "zcode";
40
+ }
41
+ if (token === "opencode" ||
42
+ token.startsWith("opencode-") ||
43
+ token.includes("opencode")) {
44
+ return "opencode";
45
+ }
46
+ }
47
+ return undefined;
48
+ }
49
+ function envPrefixHint(env) {
50
+ // Strong host-specific prefixes first (avoid OPENCODE_* false positives from compat tooling).
51
+ if (hasPrefix(env, "MIMOCODE_"))
52
+ return "mimo";
53
+ if (hasPrefix(env, "KILO_"))
54
+ return "kilo";
55
+ if (hasPrefix(env, "ZCODE_"))
56
+ return "zcode";
57
+ // OPENCODE_* alone is weak — only count when not also running under compat tooling only.
58
+ // Prefer config/binary for OpenCode; still allow OPENCODE_CONFIG_DIR as a hint.
59
+ if (env.OPENCODE_CONFIG_DIR || env.OPENCODE_CONFIG)
60
+ return "opencode";
61
+ return undefined;
62
+ }
63
+ function hasPrefix(env, prefix) {
64
+ for (const key of Object.keys(env)) {
65
+ if (key.startsWith(prefix) && env[key])
66
+ return true;
67
+ }
68
+ return false;
69
+ }
70
+ function configDirHint(options) {
71
+ const exists = options.existsSync ?? nodeExistsSync;
72
+ const drafts = [
73
+ { id: "kilo", dir: kiloProfile(options).paths.configDir },
74
+ { id: "mimo", dir: mimoProfile(options).paths.configDir },
75
+ { id: "opencode", dir: opencodeProfile(options).paths.configDir },
76
+ { id: "zcode", dir: zcodeProfile(options).paths.configDir },
77
+ ];
78
+ // Prefer more specific fork dirs before OpenCode when multiple exist.
79
+ const order = [
80
+ "kilo",
81
+ "mimo",
82
+ "zcode",
83
+ "opencode",
84
+ ];
85
+ for (const id of order) {
86
+ const entry = drafts.find((d) => d.id === id);
87
+ if (entry && exists(entry.dir))
88
+ return id;
89
+ }
90
+ return undefined;
91
+ }
92
+ /**
93
+ * Detect the current OCP host.
94
+ *
95
+ * Order (docs/ocp/0.1.md §5.1):
96
+ * 1. `OPENCODE_COMPAT_HOST`
97
+ * 2. Native binary / package identity
98
+ * 3. Config-dir heuristics
99
+ * 4. `zcode` → refuse OCP load (T0 doctor)
100
+ * 5. `unknown` → fail with doctor
101
+ */
102
+ export function detect(options = {}) {
103
+ const env = options.env ?? process.env;
104
+ const opts = { ...options, env };
105
+ const forced = env.OPENCODE_COMPAT_HOST?.trim().toLowerCase();
106
+ if (forced) {
107
+ if (forced === "unknown") {
108
+ const profile = unknownProfile(opts);
109
+ return result("unknown", profile, "env", false, unknownDoctorMessage());
110
+ }
111
+ if (isHostId(forced)) {
112
+ const profile = profileFor(forced, opts);
113
+ if (forced === "zcode") {
114
+ return result("zcode", profile, "env", false, zcodeDoctorMessage());
115
+ }
116
+ return result(forced, profile, "env", true);
117
+ }
118
+ const profile = unknownProfile(opts);
119
+ return result("unknown", profile, "env", false, `OCP: invalid OPENCODE_COMPAT_HOST=${JSON.stringify(forced)}. Expected opencode|mimo|kilo|zcode.`);
120
+ }
121
+ const fromPackage = opts.resolveNative?.();
122
+ if (fromPackage && fromPackage !== "unknown") {
123
+ if (isHostId(fromPackage)) {
124
+ const profile = profileFor(fromPackage, opts);
125
+ if (fromPackage === "zcode") {
126
+ return result("zcode", profile, "package", false, zcodeDoctorMessage());
127
+ }
128
+ return result(fromPackage, profile, "package", true);
129
+ }
130
+ }
131
+ const fromBinary = binaryHint(opts);
132
+ if (fromBinary === "zcode") {
133
+ return result("zcode", zcodeProfile(opts), "binary", false, zcodeDoctorMessage());
134
+ }
135
+ if (fromBinary) {
136
+ return result(fromBinary, profileFor(fromBinary, opts), "binary", true);
137
+ }
138
+ const fromEnvPrefix = envPrefixHint(env);
139
+ if (fromEnvPrefix) {
140
+ if (fromEnvPrefix === "zcode") {
141
+ return result("zcode", zcodeProfile(opts), "env", false, zcodeDoctorMessage());
142
+ }
143
+ return result(fromEnvPrefix, profileFor(fromEnvPrefix, opts), "env", true);
144
+ }
145
+ const fromConfig = configDirHint(opts);
146
+ if (fromConfig) {
147
+ if (fromConfig === "zcode") {
148
+ return result("zcode", zcodeProfile(opts), "config", false, zcodeDoctorMessage());
149
+ }
150
+ return result(fromConfig, profileFor(fromConfig, opts), "config", true);
151
+ }
152
+ const profile = unknownProfile(opts);
153
+ return result("unknown", profile, "fallback", false, unknownDoctorMessage());
154
+ }
155
+ //# sourceMappingURL=detect.js.map
@@ -0,0 +1,49 @@
1
+ /** T0 doctor copy for ZCode — marketplace ABI ≠ OCP. */
2
+ export declare function zcodeDoctorMessage(): string;
3
+ /** Doctor copy when no cooperating host can be identified. */
4
+ export declare function unknownDoctorMessage(): string;
5
+ /**
6
+ * Optional companion privacy pointer for doctor output.
7
+ * Docs only — OCP never mutates telemetry env/config/firewall.
8
+ */
9
+ export declare function privacyGuideHint(hostId: string): string | undefined;
10
+ /** Short capability summary for CLI doctor. */
11
+ export declare function profileSummaryLines(profile: {
12
+ id: string;
13
+ ocpVersion: string;
14
+ nativePlugin: string;
15
+ envPrefix: string;
16
+ capabilities: {
17
+ classicHooks: boolean;
18
+ promiseV2: boolean;
19
+ effectV2: boolean;
20
+ aisdkProviderHooks: boolean;
21
+ scansDotOpencode: boolean;
22
+ streamToolCallEnsure: boolean;
23
+ bashDescriptionRequired: boolean;
24
+ marketplacePlugins?: boolean;
25
+ };
26
+ hooks: {
27
+ missing: readonly string[];
28
+ extensions: readonly string[];
29
+ };
30
+ paths: {
31
+ configDir: string;
32
+ projectDirs: string[];
33
+ compatProjectDirs?: string[];
34
+ };
35
+ }): string[];
36
+ /** Join summary lines for printing. */
37
+ export declare function formatProfileSummary(profile: Parameters<typeof profileSummaryLines>[0]): string;
38
+ /** Layer A override map: `@opencode-ai/*` → `@opencode-compat/facade-*` (npm: form). */
39
+ export declare function facadeOverrides(version?: string): Record<string, string>;
40
+ /** Suggested override map snippet for plugin install trees / operator overrides. */
41
+ export declare function facadeOverrideSnippet(version?: string): string;
42
+ /** Resolve a project-relative plugin dir candidate list for a profile. */
43
+ export declare function projectPluginCandidates(profile: {
44
+ paths: {
45
+ projectDirs: string[];
46
+ compatProjectDirs?: string[];
47
+ };
48
+ }, cwd: string): string[];
49
+ //# sourceMappingURL=doctor.d.ts.map
package/dist/doctor.js ADDED
@@ -0,0 +1,90 @@
1
+ import { join } from "node:path";
2
+ /** T0 doctor copy for ZCode — marketplace ABI ≠ OCP. */
3
+ export function zcodeDoctorMessage() {
4
+ return [
5
+ "OCP: host detected as zcode (T0).",
6
+ "ZCode Agent Mode marketplace (.zcode-plugin / Claude-/Codex-style hooks) is not the OpenCode npm plugin ABI (@opencode-ai/plugin).",
7
+ "External OpenCode agent tiles that spawn the OpenCode CLI are also not OCP plugin compatibility.",
8
+ "Public marketplace examples (glm-hammer, zcode-glm-fleet) use .zcode-plugin/plugin.json — a different protocol.",
9
+ "Until Z.AI ships an OpenCode-plugin loader, OCP will not load plugins on this host.",
10
+ "Companion (not OCP): pack plugin-package skills/commands/manifests with `compat migrate-zcode` (does not migrate host MCP; see docs/plans/zcode-asset-migrator-plan.md).",
11
+ ].join(" ");
12
+ }
13
+ /** Doctor copy when no cooperating host can be identified. */
14
+ export function unknownDoctorMessage() {
15
+ return [
16
+ "OCP: could not detect a cooperating host (opencode, mimo, or kilo).",
17
+ "Set OPENCODE_COMPAT_HOST=opencode|mimo|kilo to force detection,",
18
+ "or run inside a supported CLI so binary/config heuristics can resolve the host.",
19
+ "ZCode is T0 only — see docs/ocp/0.1.md §9.",
20
+ ].join(" ");
21
+ }
22
+ /**
23
+ * Optional companion privacy pointer for doctor output.
24
+ * Docs only — OCP never mutates telemetry env/config/firewall.
25
+ */
26
+ export function privacyGuideHint(hostId) {
27
+ switch (hostId) {
28
+ case "kilo":
29
+ return "privacy: Kilo PostHog opt-out — docs/guides/kilocode-telemetry-disable.md (not an OCP kill)";
30
+ case "mimo":
31
+ return "privacy: MiMo analysis opt-out — docs/guides/mimocode-telemetry-disable.md (MIMOCODE_ENABLE_ANALYSIS=false; not an OCP kill)";
32
+ case "zcode":
33
+ return "privacy: ZCode telemetry is docs-only firewall/DNS — docs/guides/zcode-telemetry-block.md (no in-app opt-out; not an OCP kill)";
34
+ default:
35
+ return undefined;
36
+ }
37
+ }
38
+ /** Short capability summary for CLI doctor. */
39
+ export function profileSummaryLines(profile) {
40
+ const caps = profile.capabilities;
41
+ return [
42
+ `host: ${profile.id}`,
43
+ `ocp: ${profile.ocpVersion}`,
44
+ `nativePlugin: ${profile.nativePlugin}`,
45
+ `envPrefix: ${profile.envPrefix || "(none)"}`,
46
+ `configDir: ${profile.paths.configDir}`,
47
+ `projectDirs: ${profile.paths.projectDirs.join(", ") || "(none)"}`,
48
+ profile.paths.compatProjectDirs?.length
49
+ ? `compatProjectDirs: ${profile.paths.compatProjectDirs.join(", ")}`
50
+ : undefined,
51
+ `classicHooks: ${caps.classicHooks}`,
52
+ `promiseV2: ${caps.promiseV2}`,
53
+ `effectV2: ${caps.effectV2}`,
54
+ `aisdkProviderHooks: ${caps.aisdkProviderHooks}`,
55
+ `scansDotOpencode: ${caps.scansDotOpencode}`,
56
+ `streamToolCallEnsure: ${caps.streamToolCallEnsure}`,
57
+ `bashDescriptionRequired: ${caps.bashDescriptionRequired}`,
58
+ caps.marketplacePlugins ? "marketplacePlugins: true" : undefined,
59
+ profile.hooks.missing.length
60
+ ? `hooks.missing: ${profile.hooks.missing.join(", ")}`
61
+ : "hooks.missing: (none)",
62
+ profile.hooks.extensions.length
63
+ ? `hooks.extensions: ${profile.hooks.extensions.join(", ")}`
64
+ : undefined,
65
+ ].filter((line) => line !== undefined);
66
+ }
67
+ /** Join summary lines for printing. */
68
+ export function formatProfileSummary(profile) {
69
+ return profileSummaryLines(profile).join("\n");
70
+ }
71
+ /** Layer A override map: `@opencode-ai/*` → `@opencode-compat/facade-*` (npm: form). */
72
+ export function facadeOverrides(version = "0.1.0") {
73
+ return {
74
+ "@opencode-ai/plugin": `npm:@opencode-compat/facade-plugin@${version}`,
75
+ "@opencode-ai/sdk": `npm:@opencode-compat/facade-sdk@${version}`,
76
+ };
77
+ }
78
+ /** Suggested override map snippet for plugin install trees / operator overrides. */
79
+ export function facadeOverrideSnippet(version = "0.1.0") {
80
+ return JSON.stringify(facadeOverrides(version), null, 2);
81
+ }
82
+ /** Resolve a project-relative plugin dir candidate list for a profile. */
83
+ export function projectPluginCandidates(profile, cwd) {
84
+ const dirs = [
85
+ ...profile.paths.projectDirs,
86
+ ...(profile.paths.compatProjectDirs ?? []),
87
+ ];
88
+ return [...new Set(dirs)].map((d) => join(cwd, d, "plugins"));
89
+ }
90
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1,34 @@
1
+ import { type PathEnv } from "./paths";
2
+ import type { HostProfile } from "./types";
3
+ export type DraftOptions = {
4
+ env?: PathEnv;
5
+ home?: string;
6
+ };
7
+ /** OpenCode reference host — full OCP surface. */
8
+ export declare function opencodeProfile(options?: DraftOptions): HostProfile;
9
+ /**
10
+ * MiMo Code — classic T1 + OCP-layer Promise v2 (host-promise-v2), not native exports.
11
+ * Missing classic: dispose, experimental.provider.small_model (no-op + doctor).
12
+ * Stream gap: no ensureToolCall — bare tool-call drops; bash.description required.
13
+ */
14
+ export declare function mimoProfile(options?: DraftOptions): HostProfile;
15
+ /**
16
+ * Kilo Code — classic Hooks key-identical to OpenCode 1.18.3.
17
+ * Promise v2 via OCP host-promise-v2 (not native @kilocode exports).
18
+ * No project `.opencode` scan today; close via bridge docs/doctor/operator copy-symlink.
19
+ */
20
+ export declare function kiloProfile(options?: DraftOptions): HostProfile;
21
+ /**
22
+ * ZCode — T0 only. Marketplace ABI ≠ `@opencode-ai/plugin`.
23
+ */
24
+ export declare function zcodeProfile(options?: DraftOptions): HostProfile;
25
+ /** Fallback when no cooperating host is detected. */
26
+ export declare function unknownProfile(options?: DraftOptions): HostProfile;
27
+ /** All draft builders keyed by HostId (excluding unknown). */
28
+ export declare const DRAFTS: {
29
+ readonly opencode: typeof opencodeProfile;
30
+ readonly mimo: typeof mimoProfile;
31
+ readonly kilo: typeof kiloProfile;
32
+ readonly zcode: typeof zcodeProfile;
33
+ };
34
+ //# sourceMappingURL=drafts.d.ts.map
package/dist/drafts.js ADDED
@@ -0,0 +1,240 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ import { CORE_HOOKS, MIMO_EXTENSION_HOOKS, MIMO_MISSING_HOOKS, } from "./hooks";
4
+ import { resolveXdgDirs } from "./paths";
5
+ function baseOpts(options) {
6
+ const env = options?.env ?? process.env;
7
+ const home = options?.home ?? env.HOME ?? homedir();
8
+ return { env, home };
9
+ }
10
+ /** OpenCode reference host — full OCP surface. */
11
+ export function opencodeProfile(options) {
12
+ const { env, home } = baseOpts(options);
13
+ const configOverride = env.OPENCODE_CONFIG_DIR;
14
+ const xdg = resolveXdgDirs("opencode", env, home);
15
+ return {
16
+ id: "opencode",
17
+ ocpVersion: "0.1.0",
18
+ nativePlugin: "@opencode-ai/plugin",
19
+ nativeSdk: "@opencode-ai/sdk",
20
+ pluginVersionObserved: "1.18.3",
21
+ paths: {
22
+ configDir: configOverride && configOverride.length > 0 ? configOverride : xdg.configDir,
23
+ dataDir: xdg.dataDir,
24
+ cacheDir: xdg.cacheDir,
25
+ projectDirs: [".opencode"],
26
+ pluginInstallDir: join(xdg.cacheDir, "packages"),
27
+ },
28
+ configFiles: ["opencode.json", "opencode.jsonc"],
29
+ envPrefix: "OPENCODE",
30
+ capabilities: {
31
+ classicHooks: true,
32
+ promiseV2: true,
33
+ effectV2: true,
34
+ aisdkProviderHooks: true,
35
+ localPluginScan: true,
36
+ scansDotOpencode: true,
37
+ streamToolCallEnsure: true,
38
+ bashDescriptionRequired: false,
39
+ },
40
+ hooks: {
41
+ core: CORE_HOOKS,
42
+ missing: [],
43
+ extensions: [],
44
+ },
45
+ };
46
+ }
47
+ /**
48
+ * MiMo Code — classic T1 + OCP-layer Promise v2 (host-promise-v2), not native exports.
49
+ * Missing classic: dispose, experimental.provider.small_model (no-op + doctor).
50
+ * Stream gap: no ensureToolCall — bare tool-call drops; bash.description required.
51
+ */
52
+ export function mimoProfile(options) {
53
+ const { env, home } = baseOpts(options);
54
+ const mimoHome = env.MIMOCODE_HOME;
55
+ let configDir;
56
+ let dataDir;
57
+ let cacheDir;
58
+ if (mimoHome && mimoHome.length > 0) {
59
+ configDir = join(mimoHome, "config");
60
+ dataDir = join(mimoHome, "share");
61
+ cacheDir = join(mimoHome, "cache");
62
+ }
63
+ else {
64
+ const xdg = resolveXdgDirs("mimocode", env, home);
65
+ configDir = xdg.configDir;
66
+ dataDir = xdg.dataDir;
67
+ cacheDir = xdg.cacheDir;
68
+ }
69
+ return {
70
+ id: "mimo",
71
+ ocpVersion: "0.1.0",
72
+ nativePlugin: "@mimo-ai/plugin",
73
+ nativeSdk: "@mimo-ai/sdk",
74
+ pluginVersionObserved: "0.1.6",
75
+ paths: {
76
+ configDir,
77
+ dataDir,
78
+ cacheDir,
79
+ projectDirs: [".mimocode"],
80
+ compatProjectDirs: [".opencode"],
81
+ pluginInstallDir: join(cacheDir, "packages"),
82
+ },
83
+ configFiles: ["mimocode.json", "mimocode.jsonc"],
84
+ envPrefix: "MIMOCODE",
85
+ capabilities: {
86
+ classicHooks: true,
87
+ // OCP layer supplies Promise v2 via @opencode-compat/host-promise-v2 (not @mimo-ai exports)
88
+ promiseV2: true,
89
+ effectV2: false,
90
+ aisdkProviderHooks: true,
91
+ localPluginScan: true,
92
+ scansDotOpencode: false,
93
+ // MiMo SessionProcessor creates tool parts only on tool-input-start (no ensureToolCall)
94
+ streamToolCallEnsure: false,
95
+ bashDescriptionRequired: true,
96
+ },
97
+ hooks: {
98
+ core: CORE_HOOKS,
99
+ missing: MIMO_MISSING_HOOKS,
100
+ extensions: MIMO_EXTENSION_HOOKS,
101
+ },
102
+ note: "PluginInput still types createOpencodeClient from @mimo-ai/sdk (residual name); Promise v2 via OCP host kit; streamToolCallEnsure=false → OCP provider shim emits tool-input-start",
103
+ };
104
+ }
105
+ /**
106
+ * Kilo Code — classic Hooks key-identical to OpenCode 1.18.3.
107
+ * Promise v2 via OCP host-promise-v2 (not native @kilocode exports).
108
+ * No project `.opencode` scan today; close via bridge docs/doctor/operator copy-symlink.
109
+ */
110
+ export function kiloProfile(options) {
111
+ const { env, home } = baseOpts(options);
112
+ const configOverride = env.KILO_CONFIG_DIR;
113
+ const xdg = resolveXdgDirs("kilo", env, home);
114
+ const configDir = configOverride && configOverride.length > 0 ? configOverride : xdg.configDir;
115
+ return {
116
+ id: "kilo",
117
+ ocpVersion: "0.1.0",
118
+ nativePlugin: "@kilocode/plugin",
119
+ nativeSdk: "@kilocode/sdk",
120
+ pluginVersionObserved: "7.4.11",
121
+ upstreamPin: "v1.17.4",
122
+ paths: {
123
+ configDir,
124
+ dataDir: xdg.dataDir,
125
+ cacheDir: xdg.cacheDir,
126
+ projectDirs: [".kilo", ".kilocode"],
127
+ compatProjectDirs: [".opencode"],
128
+ pluginInstallDir: join(xdg.cacheDir, "packages"),
129
+ },
130
+ configFiles: [
131
+ "config.json",
132
+ "kilo.json",
133
+ "kilo.jsonc",
134
+ "opencode.json",
135
+ "opencode.jsonc",
136
+ ],
137
+ envPrefix: "KILO",
138
+ capabilities: {
139
+ classicHooks: true,
140
+ // OCP layer supplies Promise v2 via @opencode-compat/host-promise-v2 (not @kilocode exports)
141
+ promiseV2: true,
142
+ effectV2: false,
143
+ aisdkProviderHooks: true,
144
+ localPluginScan: true,
145
+ scansDotOpencode: false,
146
+ // Kilo SessionProcessor has ensureToolCall; bash.description is optional
147
+ streamToolCallEnsure: true,
148
+ bashDescriptionRequired: false,
149
+ },
150
+ hooks: {
151
+ core: CORE_HOOKS,
152
+ missing: [],
153
+ extensions: [],
154
+ },
155
+ note: "Promise v2 via OCP host kit; live host provider-resolve calls createPromiseV2Host().resolveProvider; keep stock plugins (no cursor-kilocode-provider fork)",
156
+ };
157
+ }
158
+ /**
159
+ * ZCode — T0 only. Marketplace ABI ≠ `@opencode-ai/plugin`.
160
+ */
161
+ export function zcodeProfile(options) {
162
+ const { env, home } = baseOpts(options);
163
+ const zcodeHome = env.ZCODE_HOME && env.ZCODE_HOME.length > 0
164
+ ? env.ZCODE_HOME
165
+ : join(home, ".zcode");
166
+ return {
167
+ id: "zcode",
168
+ ocpVersion: "none",
169
+ nativePlugin: "(marketplace .zcode-plugin — not @opencode-ai/plugin)",
170
+ nativeSdk: "(none — @zcode/* internals)",
171
+ pluginVersionObserved: "3.3.6",
172
+ paths: {
173
+ home: zcodeHome,
174
+ configDir: join(zcodeHome, "v2"),
175
+ dataDir: zcodeHome,
176
+ cacheDir: join(zcodeHome, "cache"),
177
+ projectDirs: [],
178
+ },
179
+ configFiles: ["setting.json", "config.json"],
180
+ envPrefix: "ZCODE",
181
+ capabilities: {
182
+ classicHooks: false,
183
+ promiseV2: false,
184
+ effectV2: false,
185
+ aisdkProviderHooks: false,
186
+ localPluginScan: false,
187
+ scansDotOpencode: false,
188
+ streamToolCallEnsure: true,
189
+ bashDescriptionRequired: false,
190
+ marketplacePlugins: true,
191
+ },
192
+ hooks: {
193
+ core: [],
194
+ missing: [...CORE_HOOKS],
195
+ extensions: [],
196
+ },
197
+ note: "External OpenCode agent tile is not OCP plugin compatibility",
198
+ };
199
+ }
200
+ /** Fallback when no cooperating host is detected. */
201
+ export function unknownProfile(options) {
202
+ const { home } = baseOpts(options);
203
+ return {
204
+ id: "unknown",
205
+ ocpVersion: "none",
206
+ nativePlugin: "(unknown)",
207
+ nativeSdk: "(unknown)",
208
+ paths: {
209
+ configDir: home,
210
+ dataDir: home,
211
+ cacheDir: home,
212
+ projectDirs: [],
213
+ },
214
+ configFiles: [],
215
+ envPrefix: "",
216
+ capabilities: {
217
+ classicHooks: false,
218
+ promiseV2: false,
219
+ effectV2: false,
220
+ aisdkProviderHooks: false,
221
+ localPluginScan: false,
222
+ scansDotOpencode: false,
223
+ streamToolCallEnsure: true,
224
+ bashDescriptionRequired: false,
225
+ },
226
+ hooks: {
227
+ core: [],
228
+ missing: [...CORE_HOOKS],
229
+ extensions: [],
230
+ },
231
+ };
232
+ }
233
+ /** All draft builders keyed by HostId (excluding unknown). */
234
+ export const DRAFTS = {
235
+ opencode: opencodeProfile,
236
+ mimo: mimoProfile,
237
+ kilo: kiloProfile,
238
+ zcode: zcodeProfile,
239
+ };
240
+ //# sourceMappingURL=drafts.js.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * OCP 0.1 portable classic Hooks core set.
3
+ * Source: docs/ocp/0.1.md §6 + docs/plans/phase0-hooks-parity.md
4
+ */
5
+ export declare const CORE_HOOKS: readonly ["dispose", "event", "config", "tool", "auth", "provider", "chat.message", "chat.params", "chat.headers", "permission.ask", "command.execute.before", "tool.execute.before", "tool.execute.after", "shell.env", "tool.definition", "experimental.chat.messages.transform", "experimental.chat.system.transform", "experimental.provider.small_model", "experimental.session.compacting", "experimental.compaction.autocontinue", "experimental.text.complete"];
6
+ export type CoreHook = (typeof CORE_HOOKS)[number];
7
+ /** MiMo host-extension hooks — non-portable (ADR-7) */
8
+ export declare const MIMO_EXTENSION_HOOKS: readonly ["actor.preStop", "actor.postStop", "session.pre", "session.post", "session.userQuery.pre", "session.userQuery.post"];
9
+ /** MiMo gaps vs OCP core */
10
+ export declare const MIMO_MISSING_HOOKS: readonly ["dispose", "experimental.provider.small_model"];
11
+ //# sourceMappingURL=hooks.d.ts.map
package/dist/hooks.js ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * OCP 0.1 portable classic Hooks core set.
3
+ * Source: docs/ocp/0.1.md §6 + docs/plans/phase0-hooks-parity.md
4
+ */
5
+ export const CORE_HOOKS = [
6
+ "dispose",
7
+ "event",
8
+ "config",
9
+ "tool",
10
+ "auth",
11
+ "provider",
12
+ "chat.message",
13
+ "chat.params",
14
+ "chat.headers",
15
+ "permission.ask",
16
+ "command.execute.before",
17
+ "tool.execute.before",
18
+ "tool.execute.after",
19
+ "shell.env",
20
+ "tool.definition",
21
+ "experimental.chat.messages.transform",
22
+ "experimental.chat.system.transform",
23
+ "experimental.provider.small_model",
24
+ "experimental.session.compacting",
25
+ "experimental.compaction.autocontinue",
26
+ "experimental.text.complete",
27
+ ];
28
+ /** MiMo host-extension hooks — non-portable (ADR-7) */
29
+ export const MIMO_EXTENSION_HOOKS = [
30
+ "actor.preStop",
31
+ "actor.postStop",
32
+ "session.pre",
33
+ "session.post",
34
+ "session.userQuery.pre",
35
+ "session.userQuery.post",
36
+ ];
37
+ /** MiMo gaps vs OCP core */
38
+ export const MIMO_MISSING_HOOKS = [
39
+ "dispose",
40
+ "experimental.provider.small_model",
41
+ ];
42
+ //# sourceMappingURL=hooks.js.map
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @opencode-compat/profile — HostProfile types, drafts, and detect().
3
+ * Contract: docs/ocp/0.1.md §5
4
+ */
5
+ export declare const PKG: "@opencode-compat/profile";
6
+ export declare const VERSION: "0.1.0";
7
+ export declare const OCP_VERSION: "0.1.0";
8
+ export type { DetectOptions, DetectResult, DetectSource, HostCapabilities, HostHooks, HostId, HostPaths, HostProfile, } from "./types";
9
+ export { CORE_HOOKS, MIMO_EXTENSION_HOOKS, MIMO_MISSING_HOOKS, type CoreHook, } from "./hooks";
10
+ export { expandHome, resolveXdgDirs } from "./paths";
11
+ export { DRAFTS, kiloProfile, mimoProfile, opencodeProfile, unknownProfile, zcodeProfile, type DraftOptions, } from "./drafts";
12
+ export { detect } from "./detect";
13
+ export { facadeOverrides, facadeOverrideSnippet, formatProfileSummary, privacyGuideHint, profileSummaryLines, projectPluginCandidates, unknownDoctorMessage, zcodeDoctorMessage, } from "./doctor";
14
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @opencode-compat/profile — HostProfile types, drafts, and detect().
3
+ * Contract: docs/ocp/0.1.md §5
4
+ */
5
+ export const PKG = "@opencode-compat/profile";
6
+ export const VERSION = "0.1.0";
7
+ export const OCP_VERSION = "0.1.0";
8
+ export { CORE_HOOKS, MIMO_EXTENSION_HOOKS, MIMO_MISSING_HOOKS, } from "./hooks";
9
+ export { expandHome, resolveXdgDirs } from "./paths";
10
+ export { DRAFTS, kiloProfile, mimoProfile, opencodeProfile, unknownProfile, zcodeProfile, } from "./drafts";
11
+ export { detect } from "./detect";
12
+ export { facadeOverrides, facadeOverrideSnippet, formatProfileSummary, privacyGuideHint, profileSummaryLines, projectPluginCandidates, unknownDoctorMessage, zcodeDoctorMessage, } from "./doctor";
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,10 @@
1
+ export type PathEnv = NodeJS.ProcessEnv;
2
+ /** Resolve OpenCode-style XDG layout for an app name. */
3
+ export declare function resolveXdgDirs(app: string, env?: PathEnv, home?: string): {
4
+ configDir: string;
5
+ dataDir: string;
6
+ cacheDir: string;
7
+ };
8
+ /** Expand a leading `~` against `home`. */
9
+ export declare function expandHome(path: string, home: string): string;
10
+ //# sourceMappingURL=paths.d.ts.map
package/dist/paths.js ADDED
@@ -0,0 +1,25 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ function xdgHome(env, key, fallback, home) {
4
+ const override = env[key];
5
+ if (override && override.length > 0)
6
+ return override;
7
+ return join(home, fallback);
8
+ }
9
+ /** Resolve OpenCode-style XDG layout for an app name. */
10
+ export function resolveXdgDirs(app, env = process.env, home = env.HOME ?? homedir()) {
11
+ return {
12
+ configDir: join(xdgHome(env, "XDG_CONFIG_HOME", ".config", home), app),
13
+ dataDir: join(xdgHome(env, "XDG_DATA_HOME", ".local/share", home), app),
14
+ cacheDir: join(xdgHome(env, "XDG_CACHE_HOME", ".cache", home), app),
15
+ };
16
+ }
17
+ /** Expand a leading `~` against `home`. */
18
+ export function expandHome(path, home) {
19
+ if (path === "~")
20
+ return home;
21
+ if (path.startsWith("~/"))
22
+ return join(home, path.slice(2));
23
+ return path;
24
+ }
25
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1,95 @@
1
+ /** OCP host identifiers — see docs/ocp/0.1.md §5 */
2
+ export type HostId = "opencode" | "mimo" | "kilo" | "zcode" | "unknown";
3
+ export type HostCapabilities = {
4
+ classicHooks: boolean;
5
+ /** Exports AND host wiring for Promise v2 */
6
+ promiseV2: boolean;
7
+ effectV2: boolean;
8
+ aisdkProviderHooks: boolean;
9
+ localPluginScan: boolean;
10
+ scansDotOpencode: boolean;
11
+ /**
12
+ * Host SessionProcessor lazily creates tool parts on bare `tool-call`
13
+ * (OpenCode / Kilo `ensureToolCall`). When false (MiMo), OCP must emit
14
+ * `tool-input-start` before `tool-call` for custom LanguageModel streams.
15
+ */
16
+ streamToolCallEnsure: boolean;
17
+ /**
18
+ * Host `bash` tool schema requires a string `description`. When true (MiMo),
19
+ * OCP may fill a missing description on bash tool-call inputs only.
20
+ */
21
+ bashDescriptionRequired: boolean;
22
+ /** ZCode marketplace ABI — not OCP */
23
+ marketplacePlugins?: boolean;
24
+ };
25
+ export type HostPaths = {
26
+ configDir: string;
27
+ dataDir: string;
28
+ cacheDir: string;
29
+ /** Project dirs the host actually scans today */
30
+ projectDirs: string[];
31
+ /** Recommended compat project dirs for matrix/doctor (e.g. `.opencode`) */
32
+ compatProjectDirs?: string[];
33
+ /** npm plugin install cache, when distinct from cacheDir */
34
+ pluginInstallDir?: string;
35
+ /** Absolute home root when the host uses a non-XDG layout (ZCode) */
36
+ home?: string;
37
+ };
38
+ export type HostHooks = {
39
+ /** Portable classic hooks implemented (or accepted via facade) */
40
+ core: readonly string[];
41
+ /** Core hooks absent on this host (compat gaps) */
42
+ missing: readonly string[];
43
+ /** Host-only hooks — never required for portable plugins */
44
+ extensions: readonly string[];
45
+ };
46
+ export type HostProfile = {
47
+ id: HostId;
48
+ /** OCP semver this profile targets, or `"none"` for T0 hosts */
49
+ ocpVersion: string;
50
+ nativePlugin: string;
51
+ nativeSdk: string;
52
+ /** Observed native plugin package version at research time */
53
+ pluginVersionObserved?: string;
54
+ upstreamPin?: string;
55
+ paths: HostPaths;
56
+ configFiles: readonly string[];
57
+ /** Env prefix without trailing underscore, e.g. `OPENCODE` */
58
+ envPrefix: string;
59
+ capabilities: HostCapabilities;
60
+ hooks: HostHooks;
61
+ agents?: {
62
+ builtins: string[];
63
+ aliases?: Record<string, string>;
64
+ };
65
+ /** Free-form research notes */
66
+ note?: string;
67
+ };
68
+ export type DetectSource = "env" | "binary" | "package" | "config" | "fallback";
69
+ export type DetectResult = {
70
+ id: HostId;
71
+ profile: HostProfile;
72
+ source: DetectSource;
73
+ /**
74
+ * Whether OCP plugin load is allowed.
75
+ * `false` for `zcode` (T0) and `unknown`.
76
+ */
77
+ supported: boolean;
78
+ /** Doctor / refusal text when `supported` is false */
79
+ message?: string;
80
+ };
81
+ export type DetectOptions = {
82
+ env?: NodeJS.ProcessEnv;
83
+ home?: string;
84
+ cwd?: string;
85
+ argv?: readonly string[];
86
+ execPath?: string;
87
+ /** Injectable for tests */
88
+ existsSync?: (path: string) => boolean;
89
+ /**
90
+ * Injectable package-presence probe (e.g. try resolve native plugin).
91
+ * Return the HostId when a native package is detectable.
92
+ */
93
+ resolveNative?: () => HostId | undefined;
94
+ };
95
+ //# sourceMappingURL=types.d.ts.map
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@opencode-compat/profile",
3
+ "version": "0.1.0",
4
+ "description": "HostProfile types and draft profiles for OCP hosts",
5
+ "type": "module",
6
+ "license": "MPL-2.0",
7
+ "author": "oakimov",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/oakimov/opencode-plugin-compat.git",
11
+ "directory": "packages/profile"
12
+ },
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js"
22
+ }
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "LICENSE",
27
+ "README.md",
28
+ "!dist/**/*.map"
29
+ ],
30
+ "scripts": {
31
+ "build": "bunx tsc -p tsconfig.json",
32
+ "typecheck": "bunx tsc -p tsconfig.json --noEmit",
33
+ "prepack": "bunx tsc -p tsconfig.json"
34
+ },
35
+ "dependencies": {},
36
+ "devDependencies": {
37
+ "typescript": "^5.8.3"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/oakimov/opencode-plugin-compat/issues"
41
+ },
42
+ "homepage": "https://github.com/oakimov/opencode-plugin-compat/tree/main/packages/profile#readme",
43
+ "keywords": [
44
+ "opencode",
45
+ "ocp",
46
+ "hostprofile",
47
+ "mimocode",
48
+ "kilocode"
49
+ ],
50
+ "engines": {
51
+ "bun": ">=1.2.0"
52
+ }
53
+ }