codiedev 0.9.25 → 0.9.28
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 +32 -4
- package/assets/opencode-plugin.mjs +54 -0
- package/assets/tui-brand-plugin.tsx +63 -0
- package/dist/backup/cache.js +2 -2
- package/dist/backup/run.js +5 -0
- package/dist/captureExclusions.d.ts +30 -0
- package/dist/captureExclusions.js +118 -0
- package/dist/captureLog.js +6 -3
- package/dist/cli.js +38 -0
- package/dist/codie.d.ts +2 -0
- package/dist/codie.js +233 -0
- package/dist/codieLauncher.d.ts +41 -0
- package/dist/codieLauncher.js +352 -0
- package/dist/codieRuntime.d.ts +19 -0
- package/dist/codieRuntime.js +91 -0
- package/dist/codieRuntimeInstall.d.ts +27 -0
- package/dist/codieRuntimeInstall.js +145 -0
- package/dist/commands/capture.d.ts +14 -0
- package/dist/commands/capture.js +148 -0
- package/dist/configDir.d.ts +1 -0
- package/dist/configDir.js +54 -0
- package/dist/connect.js +27 -2
- package/dist/hook.js +77 -43
- package/dist/upgradeNudge.js +6 -5
- package/dist/utils.d.ts +30 -3
- package/dist/utils.js +313 -22
- package/package.json +13 -5
package/dist/codie.js
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
// `codie` — Codiedev's governed coding harness.
|
|
4
|
+
//
|
|
5
|
+
// Ships from the same package as `codiedev` so a developer runs one install and
|
|
6
|
+
// then picks their surface: Claude Code, Codex, and Cursor get session capture
|
|
7
|
+
// via `codiedev connect`; `codie` launches the governed harness against the
|
|
8
|
+
// same token. OpenCode is the embedded engine; all user-facing identity,
|
|
9
|
+
// credentials, models, policy, and capture belong to Codiedev.
|
|
10
|
+
//
|
|
11
|
+
// The engine is downloaded on first run (see codieRuntime.ts) so capture-only
|
|
12
|
+
// users never pay for it.
|
|
13
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
+
}
|
|
19
|
+
Object.defineProperty(o, k2, desc);
|
|
20
|
+
}) : (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
o[k2] = m[k];
|
|
23
|
+
}));
|
|
24
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
+
}) : function(o, v) {
|
|
27
|
+
o["default"] = v;
|
|
28
|
+
});
|
|
29
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
30
|
+
var ownKeys = function(o) {
|
|
31
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
32
|
+
var ar = [];
|
|
33
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
+
return ar;
|
|
35
|
+
};
|
|
36
|
+
return ownKeys(o);
|
|
37
|
+
};
|
|
38
|
+
return function (mod) {
|
|
39
|
+
if (mod && mod.__esModule) return mod;
|
|
40
|
+
var result = {};
|
|
41
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
42
|
+
__setModuleDefault(result, mod);
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
})();
|
|
46
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
const fs = __importStar(require("fs"));
|
|
48
|
+
const os = __importStar(require("os"));
|
|
49
|
+
const path = __importStar(require("path"));
|
|
50
|
+
const child_process_1 = require("child_process");
|
|
51
|
+
const url_1 = require("url");
|
|
52
|
+
const configDir_1 = require("./configDir");
|
|
53
|
+
const codieLauncher_1 = require("./codieLauncher");
|
|
54
|
+
const codieRuntime_1 = require("./codieRuntime");
|
|
55
|
+
const codieRuntimeInstall_1 = require("./codieRuntimeInstall");
|
|
56
|
+
function git(args) {
|
|
57
|
+
const result = (0, child_process_1.spawnSync)("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
58
|
+
return result.status === 0 ? result.stdout.trim() : "";
|
|
59
|
+
}
|
|
60
|
+
function safeRepoUrl(value) {
|
|
61
|
+
if (!value)
|
|
62
|
+
return "";
|
|
63
|
+
try {
|
|
64
|
+
const url = new URL(value);
|
|
65
|
+
url.username = "";
|
|
66
|
+
url.password = "";
|
|
67
|
+
return url.toString();
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return value.replace(/^[^@\s]+@(?=[^:/\s]+[:/])/, "");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function existingInlineConfig() {
|
|
74
|
+
const raw = process.env.OPENCODE_CONFIG_CONTENT;
|
|
75
|
+
if (!raw)
|
|
76
|
+
return {};
|
|
77
|
+
try {
|
|
78
|
+
return JSON.parse(raw);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
throw new Error("OPENCODE_CONFIG_CONTENT is not valid JSON");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// The one credential. `codiedev connect` wrote it; the gateway validates the
|
|
85
|
+
// same userApiTokens row, so there is no second harness token to mint.
|
|
86
|
+
function connectedCodiedevConfig() {
|
|
87
|
+
try {
|
|
88
|
+
return JSON.parse(fs.readFileSync(path.join((0, configDir_1.codiedevStateDir)(), "config.json"), "utf8"));
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return {};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function assetPath(name) {
|
|
95
|
+
// dist/codie.js → ../assets/<name>; src/codie.ts → ../assets/<name>.
|
|
96
|
+
return path.join(__dirname, "..", "assets", name);
|
|
97
|
+
}
|
|
98
|
+
// Stage the home-screen branding plugin. TUI plugins only load from tui.json
|
|
99
|
+
// in the OpenCode config dir, so the packaged file is copied next to the
|
|
100
|
+
// capture plugin and referenced with a relative spec. Best-effort: any
|
|
101
|
+
// failure (e.g. a hand-edited tui.json with comments) leaves the stock logo.
|
|
102
|
+
function installBrandPlugin() {
|
|
103
|
+
try {
|
|
104
|
+
// OpenCode resolves its config dir per the XDG spec, so a customized
|
|
105
|
+
// XDG_CONFIG_HOME must be honored or tui.json lands where it never reads.
|
|
106
|
+
const configDir = path.join(process.env.XDG_CONFIG_HOME?.trim() || path.join(os.homedir(), ".config"), "opencode");
|
|
107
|
+
const pluginDir = path.join(configDir, "plugins");
|
|
108
|
+
fs.mkdirSync(pluginDir, { recursive: true });
|
|
109
|
+
const source = fs.readFileSync(assetPath("tui-brand-plugin.tsx"), "utf8");
|
|
110
|
+
const destination = path.join(pluginDir, "codiedev-brand.tsx");
|
|
111
|
+
if (!fs.existsSync(destination) || fs.readFileSync(destination, "utf8") !== source) {
|
|
112
|
+
fs.writeFileSync(destination, source);
|
|
113
|
+
}
|
|
114
|
+
const tuiConfigPath = path.join(configDir, "tui.json");
|
|
115
|
+
const existing = fs.existsSync(tuiConfigPath) ? JSON.parse(fs.readFileSync(tuiConfigPath, "utf8")) : {};
|
|
116
|
+
const { config, changed } = (0, codieLauncher_1.withBrandPlugin)(existing);
|
|
117
|
+
if (changed)
|
|
118
|
+
fs.writeFileSync(tuiConfigPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
console.error(`codie: continuing without Codiedev branding (${error instanceof Error ? error.message : error})`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Remove the engine's boot/exit banner wordmark in place (see
|
|
125
|
+
// patchBrandWordmark). Best-effort like the TUI plugin: any failure rolls the
|
|
126
|
+
// bytes back and the stock banner keeps working. Runs once per install; an
|
|
127
|
+
// already-suppressed binary has no known source region and is skipped.
|
|
128
|
+
function installBrandWordmark(runtime) {
|
|
129
|
+
try {
|
|
130
|
+
installBrandWordmarkUnsafe(runtime);
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
console.error(`codie: continuing with the stock banner (${error instanceof Error ? error.message : error})`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function installBrandWordmarkUnsafe(runtime) {
|
|
137
|
+
const binary = fs.readFileSync(runtime);
|
|
138
|
+
const { changed, offset, replacement } = (0, codieLauncher_1.patchBrandWordmark)(binary);
|
|
139
|
+
if (!changed || !replacement)
|
|
140
|
+
return;
|
|
141
|
+
// In-place write (not copy+rename): opencode-ai/bin/opencode.exe and the
|
|
142
|
+
// platform package's binary are hardlinks to one inode, and this keeps them
|
|
143
|
+
// consistent. Restoring the original bytes is a true rollback — identical
|
|
144
|
+
// content revalidates the existing macOS signature.
|
|
145
|
+
const writeAt = (bytes) => {
|
|
146
|
+
const fd = fs.openSync(runtime, "r+");
|
|
147
|
+
try {
|
|
148
|
+
fs.writeSync(fd, bytes, 0, bytes.length, offset);
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
fs.closeSync(fd);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
const restore = () => writeAt(binary.subarray(offset, offset + replacement.length));
|
|
155
|
+
writeAt(replacement);
|
|
156
|
+
if (process.platform === "darwin") {
|
|
157
|
+
// A modified Mach-O needs a fresh ad-hoc signature or the OS kills it.
|
|
158
|
+
const signed = (0, child_process_1.spawnSync)("codesign", ["--force", "--sign", "-", runtime], { stdio: "ignore" });
|
|
159
|
+
if (signed.status !== 0) {
|
|
160
|
+
restore();
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const probe = (0, child_process_1.spawnSync)(runtime, ["--version"], { stdio: "ignore", timeout: 30000 });
|
|
165
|
+
if (probe.status !== 0) {
|
|
166
|
+
restore();
|
|
167
|
+
if (process.platform === "darwin")
|
|
168
|
+
(0, child_process_1.spawnSync)("codesign", ["--force", "--sign", "-", runtime], { stdio: "ignore" });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async function main() {
|
|
172
|
+
(0, codieRuntime_1.assertCodieNodeVersion)(process.versions.node);
|
|
173
|
+
const connected = connectedCodiedevConfig();
|
|
174
|
+
const key = process.env.CODIEDEV_GATEWAY_KEY ||
|
|
175
|
+
process.env.CODIEDEV_TOKEN ||
|
|
176
|
+
(typeof connected.token === "string" ? connected.token.trim() : "");
|
|
177
|
+
if (!key) {
|
|
178
|
+
throw new Error("Connect your Codiedev account first with `codiedev connect`");
|
|
179
|
+
}
|
|
180
|
+
const gatewayUrl = process.env.CODIEDEV_GATEWAY_URL || "https://gateway.codiedev.com/api/v1";
|
|
181
|
+
const catalog = await (0, codieLauncher_1.fetchApprovedCatalog)({ baseUrl: gatewayUrl, key });
|
|
182
|
+
const parsed = (0, codieLauncher_1.parseCodieArgs)(process.argv.slice(2));
|
|
183
|
+
if (parsed.args.length === 1 && parsed.args[0] === "models") {
|
|
184
|
+
console.log("Codiedev approved models\n");
|
|
185
|
+
catalog.forEach((model) => console.log(` ${model.id.padEnd(38)} ${model.name || ""}`));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
(0, codieLauncher_1.validateCodieArgs)(parsed.args, catalog);
|
|
189
|
+
const stateDir = (0, configDir_1.codiedevStateDir)();
|
|
190
|
+
const runtime = (0, codieRuntimeInstall_1.ensureCodieRuntimeBinary)({
|
|
191
|
+
resolve: () => (0, codieRuntime_1.resolveCodieRuntime)({
|
|
192
|
+
env: process.env,
|
|
193
|
+
stateDir,
|
|
194
|
+
existsSync: (candidate) => fs.existsSync(candidate),
|
|
195
|
+
resolveBundled: () => {
|
|
196
|
+
try {
|
|
197
|
+
return require.resolve("opencode-ai/bin/opencode.exe");
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
}),
|
|
204
|
+
install: (missing) => (0, codieRuntimeInstall_1.installCodieRuntimeOnDisk)(missing, stateDir),
|
|
205
|
+
});
|
|
206
|
+
const metadataPlugin = (0, url_1.pathToFileURL)(assetPath("opencode-plugin.mjs")).href;
|
|
207
|
+
const capturePlugin = (0, codieLauncher_1.resolveCapturePlugin)({ homeDir: os.homedir(), existsSync: fs.existsSync });
|
|
208
|
+
installBrandPlugin();
|
|
209
|
+
installBrandWordmark(runtime);
|
|
210
|
+
const config = (0, codieLauncher_1.buildCodieConfig)({
|
|
211
|
+
existing: existingInlineConfig(),
|
|
212
|
+
catalog,
|
|
213
|
+
env: { ...process.env, ...parsed.overrides, CODIEDEV_GATEWAY_URL: gatewayUrl },
|
|
214
|
+
plugins: [capturePlugin, metadataPlugin],
|
|
215
|
+
});
|
|
216
|
+
const env = {
|
|
217
|
+
...process.env,
|
|
218
|
+
...parsed.overrides,
|
|
219
|
+
CODIEDEV_GATEWAY_KEY: key,
|
|
220
|
+
CODIEDEV_REPO_URL: safeRepoUrl(git(["remote", "get-url", "origin"])),
|
|
221
|
+
CODIEDEV_BRANCH: git(["branch", "--show-current"]),
|
|
222
|
+
CODIEDEV_HEAD_SHA: git(["rev-parse", "HEAD"]),
|
|
223
|
+
OPENCODE_CONFIG_CONTENT: JSON.stringify(config),
|
|
224
|
+
};
|
|
225
|
+
const result = (0, child_process_1.spawnSync)(runtime, parsed.args, { stdio: "inherit", env });
|
|
226
|
+
if (result.error)
|
|
227
|
+
throw result.error;
|
|
228
|
+
process.exitCode = result.status === null ? 1 : result.status;
|
|
229
|
+
}
|
|
230
|
+
main().catch((error) => {
|
|
231
|
+
console.error(`codie: ${error instanceof Error ? error.message : String(error)}`);
|
|
232
|
+
process.exitCode = 1;
|
|
233
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export interface CatalogModel {
|
|
2
|
+
id: string;
|
|
3
|
+
name?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function bareModel(value: unknown): string;
|
|
6
|
+
export declare function buildCodieConfig({ existing, catalog, env, plugin, plugins, }: {
|
|
7
|
+
existing?: Record<string, any>;
|
|
8
|
+
catalog: CatalogModel[];
|
|
9
|
+
env?: Record<string, string | undefined>;
|
|
10
|
+
plugin?: string;
|
|
11
|
+
plugins?: string[];
|
|
12
|
+
}): Record<string, any>;
|
|
13
|
+
export declare const BRAND_PLUGIN_SPEC = "./plugins/codiedev-brand.tsx";
|
|
14
|
+
export declare function withBrandPlugin(existing: Record<string, any> | undefined): {
|
|
15
|
+
config: Record<string, any>;
|
|
16
|
+
changed: boolean;
|
|
17
|
+
};
|
|
18
|
+
export declare const STOCK_WORDMARK: string;
|
|
19
|
+
export declare const LEGACY_CODIEDEV_WORDMARK: string;
|
|
20
|
+
export declare const CODIEDEV_WORDMARK: string;
|
|
21
|
+
export declare const HIDDEN_WORDMARK: string;
|
|
22
|
+
export declare function patchBrandWordmark(binary: Buffer): {
|
|
23
|
+
changed: boolean;
|
|
24
|
+
offset: number;
|
|
25
|
+
stock: Buffer;
|
|
26
|
+
replacement: Buffer | null;
|
|
27
|
+
};
|
|
28
|
+
export declare function resolveCapturePlugin({ homeDir, existsSync, }: {
|
|
29
|
+
homeDir: string;
|
|
30
|
+
existsSync: (candidate: string) => boolean;
|
|
31
|
+
}): string;
|
|
32
|
+
export declare function fetchApprovedCatalog({ baseUrl, key, fetchImpl, }: {
|
|
33
|
+
baseUrl?: string;
|
|
34
|
+
key: string;
|
|
35
|
+
fetchImpl?: typeof fetch;
|
|
36
|
+
}): Promise<CatalogModel[]>;
|
|
37
|
+
export declare function parseCodieArgs(argv: string[]): {
|
|
38
|
+
args: string[];
|
|
39
|
+
overrides: Record<string, string>;
|
|
40
|
+
};
|
|
41
|
+
export declare function validateCodieArgs(args: string[], catalog: CatalogModel[]): void;
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Pure launcher logic for the Codie harness.
|
|
3
|
+
//
|
|
4
|
+
// Ported from codiedev-gateway-cli/npm/lib/launcher.js when the harness folded
|
|
5
|
+
// into this package so `npm i -g codiedev` is the only install a developer
|
|
6
|
+
// needs — Claude Code, Codex, Cursor, and Codie all come from one package and
|
|
7
|
+
// share the one token `codiedev connect` stored.
|
|
8
|
+
//
|
|
9
|
+
// Everything here is pure (no fs/network side effects beyond an injected
|
|
10
|
+
// fetch) so the governance rules can be tested without a live gateway.
|
|
11
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
15
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
16
|
+
}
|
|
17
|
+
Object.defineProperty(o, k2, desc);
|
|
18
|
+
}) : (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
o[k2] = m[k];
|
|
21
|
+
}));
|
|
22
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
23
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
24
|
+
}) : function(o, v) {
|
|
25
|
+
o["default"] = v;
|
|
26
|
+
});
|
|
27
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
28
|
+
var ownKeys = function(o) {
|
|
29
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
30
|
+
var ar = [];
|
|
31
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
32
|
+
return ar;
|
|
33
|
+
};
|
|
34
|
+
return ownKeys(o);
|
|
35
|
+
};
|
|
36
|
+
return function (mod) {
|
|
37
|
+
if (mod && mod.__esModule) return mod;
|
|
38
|
+
var result = {};
|
|
39
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
40
|
+
__setModuleDefault(result, mod);
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
43
|
+
})();
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.HIDDEN_WORDMARK = exports.CODIEDEV_WORDMARK = exports.LEGACY_CODIEDEV_WORDMARK = exports.STOCK_WORDMARK = exports.BRAND_PLUGIN_SPEC = void 0;
|
|
46
|
+
exports.bareModel = bareModel;
|
|
47
|
+
exports.buildCodieConfig = buildCodieConfig;
|
|
48
|
+
exports.withBrandPlugin = withBrandPlugin;
|
|
49
|
+
exports.patchBrandWordmark = patchBrandWordmark;
|
|
50
|
+
exports.resolveCapturePlugin = resolveCapturePlugin;
|
|
51
|
+
exports.fetchApprovedCatalog = fetchApprovedCatalog;
|
|
52
|
+
exports.parseCodieArgs = parseCodieArgs;
|
|
53
|
+
exports.validateCodieArgs = validateCodieArgs;
|
|
54
|
+
const path = __importStar(require("path"));
|
|
55
|
+
const url_1 = require("url");
|
|
56
|
+
const DEFAULT_NAMES = {
|
|
57
|
+
"openai/gpt-5.3-codex": "GPT-5.3 Codex",
|
|
58
|
+
"openai/gpt-5.2-codex": "GPT-5.2 Codex",
|
|
59
|
+
"openai/gpt-5.1-codex-mini": "GPT-5.1 Codex Mini",
|
|
60
|
+
"anthropic/claude-sonnet-4.6": "Claude Sonnet 4.6",
|
|
61
|
+
"z-ai/glm-5.2": "GLM 5.2 · Fireworks ZDR",
|
|
62
|
+
"moonshotai/kimi-k2.7-code": "Kimi K2.7 Code · Fireworks ZDR",
|
|
63
|
+
};
|
|
64
|
+
function cleanBaseUrl(value) {
|
|
65
|
+
return String(value || "https://gateway.codiedev.com/api/v1").replace(/\/+$/, "");
|
|
66
|
+
}
|
|
67
|
+
function bareModel(value) {
|
|
68
|
+
return String(value || "").trim().replace(/^codiedev\//, "");
|
|
69
|
+
}
|
|
70
|
+
function governedModel(value, approved, fallback, label) {
|
|
71
|
+
const model = bareModel(value || fallback);
|
|
72
|
+
if (!approved.has(model)) {
|
|
73
|
+
throw new Error(`${label} model is not approved for this Codiedev account: ${model || "(empty)"}`);
|
|
74
|
+
}
|
|
75
|
+
return `codiedev/${model}`;
|
|
76
|
+
}
|
|
77
|
+
function economicalModel(catalog, primary) {
|
|
78
|
+
// Catalog ids arrive already normalized through bareModel(), so the
|
|
79
|
+
// gateway's `codiedev/fast` alias appears here as plain "fast".
|
|
80
|
+
const preferred = ["openai/gpt-5.1-codex-mini", "fast", "z-ai/glm-5.2"];
|
|
81
|
+
return preferred.find((id) => catalog.some((model) => model.id === id)) || primary;
|
|
82
|
+
}
|
|
83
|
+
function normalizeAgentModels(agents, approved, fallback) {
|
|
84
|
+
const normalized = {};
|
|
85
|
+
for (const [name, definition] of Object.entries(agents || {})) {
|
|
86
|
+
const next = definition && typeof definition === "object" ? { ...definition } : definition;
|
|
87
|
+
if (next && typeof next === "object" && next.model) {
|
|
88
|
+
next.model = governedModel(next.model, approved, fallback, `Agent “${name}”`);
|
|
89
|
+
}
|
|
90
|
+
normalized[name] = next;
|
|
91
|
+
}
|
|
92
|
+
return normalized;
|
|
93
|
+
}
|
|
94
|
+
function buildCodieConfig({ existing = {}, catalog, env = {}, plugin, plugins = [], }) {
|
|
95
|
+
if (!Array.isArray(catalog) || catalog.length === 0) {
|
|
96
|
+
throw new Error("No company-approved models were returned by Codiedev");
|
|
97
|
+
}
|
|
98
|
+
const approved = new Set(catalog.map((model) => bareModel(model.id)).filter(Boolean));
|
|
99
|
+
const first = bareModel(catalog[0].id);
|
|
100
|
+
const requestedPrimary = env.CODIEDEV_PRIMARY_MODEL || env.CODIEDEV_MODEL || existing.model || first;
|
|
101
|
+
const primary = bareModel(requestedPrimary);
|
|
102
|
+
const fast = economicalModel(catalog, primary);
|
|
103
|
+
const requestedSubagent = env.CODIEDEV_SUBAGENT_MODEL || fast;
|
|
104
|
+
const requestedPlan = env.CODIEDEV_PLAN_MODEL || requestedSubagent;
|
|
105
|
+
const agents = normalizeAgentModels(existing.agent, approved, primary);
|
|
106
|
+
const subagentModel = governedModel(requestedSubagent, approved, fast, "Subagent");
|
|
107
|
+
const planModel = governedModel(requestedPlan, approved, fast, "Plan agent");
|
|
108
|
+
const existingPlugins = Array.isArray(existing.plugin) ? existing.plugin : [];
|
|
109
|
+
const requiredPlugins = [...plugins, plugin].filter(Boolean);
|
|
110
|
+
const models = Object.fromEntries(catalog.map((model) => [
|
|
111
|
+
bareModel(model.id),
|
|
112
|
+
{ name: model.name || DEFAULT_NAMES[bareModel(model.id)] || bareModel(model.id) },
|
|
113
|
+
]));
|
|
114
|
+
return {
|
|
115
|
+
...existing,
|
|
116
|
+
model: governedModel(primary, approved, first, "Primary agent"),
|
|
117
|
+
enabled_providers: ["codiedev"],
|
|
118
|
+
share: "disabled",
|
|
119
|
+
provider: {
|
|
120
|
+
...(existing.provider || {}),
|
|
121
|
+
codiedev: {
|
|
122
|
+
npm: "@ai-sdk/openai-compatible",
|
|
123
|
+
name: "Codiedev Models",
|
|
124
|
+
options: {
|
|
125
|
+
baseURL: cleanBaseUrl(env.CODIEDEV_GATEWAY_URL),
|
|
126
|
+
apiKey: "{env:CODIEDEV_GATEWAY_KEY}",
|
|
127
|
+
},
|
|
128
|
+
models,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
agent: {
|
|
132
|
+
...agents,
|
|
133
|
+
plan: { ...(agents.plan || {}), model: agents.plan?.model || planModel },
|
|
134
|
+
general: { ...(agents.general || {}), model: agents.general?.model || subagentModel },
|
|
135
|
+
explore: { ...(agents.explore || {}), model: agents.explore?.model || subagentModel },
|
|
136
|
+
scout: { ...(agents.scout || {}), model: agents.scout?.model || subagentModel },
|
|
137
|
+
},
|
|
138
|
+
plugin: [...existingPlugins.filter((item) => !requiredPlugins.includes(item)), ...requiredPlugins],
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
exports.BRAND_PLUGIN_SPEC = "./plugins/codiedev-brand.tsx";
|
|
142
|
+
// Pure merge for ~/.config/opencode/tui.json: TUI plugins load from tui.json,
|
|
143
|
+
// not the opencode.json we inject via OPENCODE_CONFIG_CONTENT.
|
|
144
|
+
function withBrandPlugin(existing) {
|
|
145
|
+
const config = existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {};
|
|
146
|
+
const plugins = Array.isArray(config.plugin) ? config.plugin : [];
|
|
147
|
+
const present = plugins.some((item) => (Array.isArray(item) ? item[0] : item) === exports.BRAND_PLUGIN_SPEC);
|
|
148
|
+
if (present)
|
|
149
|
+
return { config, changed: false };
|
|
150
|
+
return { config: { ...config, plugin: [...plugins, exports.BRAND_PLUGIN_SPEC] }, changed: true };
|
|
151
|
+
}
|
|
152
|
+
// ── Engine wordmark suppression ─────────────────────────────────────────
|
|
153
|
+
// The OpenCode engine prints a block-letter "opencode" wordmark to terminal
|
|
154
|
+
// scrollback around each session (the boot/exit banner). A four-row terminal
|
|
155
|
+
// raster cannot reproduce the real Codiedev wordmark cleanly, so leave that
|
|
156
|
+
// surface blank and keep the proper brand mark on the plugin-driven home
|
|
157
|
+
// screen. The bundled JS table is replaced byte-for-byte so binary offsets do
|
|
158
|
+
// not move.
|
|
159
|
+
function escapeJsSource(row) {
|
|
160
|
+
let out = "";
|
|
161
|
+
for (const ch of row) {
|
|
162
|
+
const code = ch.codePointAt(0);
|
|
163
|
+
out += code > 127 ? `\\u${code.toString(16).padStart(4, "0")}` : ch;
|
|
164
|
+
}
|
|
165
|
+
return out;
|
|
166
|
+
}
|
|
167
|
+
function wordmarkRegion(left, right, { escapeUnicode = true } = {}) {
|
|
168
|
+
const rows = (list) => list.map((row) => `"${escapeUnicode ? escapeJsSource(row) : row}"`).join(",");
|
|
169
|
+
return `var vn={left:[${rows(left)}],right:[${rows(right)}]};`;
|
|
170
|
+
}
|
|
171
|
+
// o p e n | c o d e — must match the engine's bundle byte-for-byte.
|
|
172
|
+
exports.STOCK_WORDMARK = wordmarkRegion([
|
|
173
|
+
" ",
|
|
174
|
+
"█▀▀█ █▀▀█ █▀▀█ █▀▀▄",
|
|
175
|
+
"█__█ █__█ █^^^ █__█",
|
|
176
|
+
"▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀~~▀",
|
|
177
|
+
], [
|
|
178
|
+
" ▄ ",
|
|
179
|
+
"█▀▀▀ █▀▀█ █▀▀█ █▀▀█",
|
|
180
|
+
"█___ █__█ █__█ █^^^",
|
|
181
|
+
"▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀",
|
|
182
|
+
]);
|
|
183
|
+
// c o d i | e d e v, rasterized from public/codiedevlogo.svg at eight vertical
|
|
184
|
+
// pixels and packed into four terminal rows with half-block characters. This
|
|
185
|
+
// preserves the real mark's round lowercase bowls, linked c/o, ascenders, and
|
|
186
|
+
// compact v instead of approximating the letters with OpenCode's square font.
|
|
187
|
+
// The engine inserts one column between left and right, so the blank source
|
|
188
|
+
// column at the codi/edev boundary is intentionally omitted.
|
|
189
|
+
const CODIE_LEFT = [
|
|
190
|
+
" ▄ ▄",
|
|
191
|
+
" ▄▄█▄▄ ▄▄█▄▄ ▄████ █",
|
|
192
|
+
" █ ▄██ █ █ ██ █",
|
|
193
|
+
" ▀▀█▀▀ ▀▀█▀▀ ▀▀▀▀ ▀",
|
|
194
|
+
];
|
|
195
|
+
const CODIE_RIGHT = [
|
|
196
|
+
" ▄ ",
|
|
197
|
+
"▄▄█▄▄ ▄▄█▄█ ▄██▄▄▄ ▄▄ ",
|
|
198
|
+
"██▀▀█ █ █ ██▄▀█ ▀█▄█ ",
|
|
199
|
+
"▀▀█▀▀ ▀▀▀▀▀ ▀▀▀▀ ▀▀ ",
|
|
200
|
+
];
|
|
201
|
+
// Migration source for the first hand-drawn banner shipped during development.
|
|
202
|
+
// Recognizing it lets an existing install upgrade in place instead of keeping
|
|
203
|
+
// the old square letterforms until OpenCode happens to reinstall its binary.
|
|
204
|
+
const LEGACY_CODIE_LEFT = [
|
|
205
|
+
" ▄ ▄ ",
|
|
206
|
+
"█▀▀▀ █▀▀█ █▀▀█ █ ",
|
|
207
|
+
"█___ █__█ █__█ █ ",
|
|
208
|
+
"▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀ ",
|
|
209
|
+
];
|
|
210
|
+
const LEGACY_CODIE_RIGHT = [
|
|
211
|
+
" ▄ ",
|
|
212
|
+
"█▀▀█ █▀▀█ █▀▀█ █__█",
|
|
213
|
+
"█^^^ █__█ █^^^ █__█",
|
|
214
|
+
"▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀ ",
|
|
215
|
+
];
|
|
216
|
+
exports.LEGACY_CODIEDEV_WORDMARK = (() => {
|
|
217
|
+
const unpadded = wordmarkRegion(LEGACY_CODIE_LEFT, LEGACY_CODIE_RIGHT);
|
|
218
|
+
const deficit = Buffer.byteLength(exports.STOCK_WORDMARK) - Buffer.byteLength(unpadded);
|
|
219
|
+
const share = Math.floor(deficit / LEGACY_CODIE_RIGHT.length);
|
|
220
|
+
const extra = deficit % LEGACY_CODIE_RIGHT.length;
|
|
221
|
+
const padded = LEGACY_CODIE_RIGHT.map((row, index) => row + " ".repeat(share + (index < extra ? 1 : 0)));
|
|
222
|
+
return wordmarkRegion(LEGACY_CODIE_LEFT, padded);
|
|
223
|
+
})();
|
|
224
|
+
exports.CODIEDEV_WORDMARK = (() => {
|
|
225
|
+
// Raw Unicode is valid JavaScript and costs fewer source bytes than \uXXXX.
|
|
226
|
+
// Put the remaining byte padding outside the strings so it cannot widen the
|
|
227
|
+
// rendered banner or shift its centering.
|
|
228
|
+
const unpadded = wordmarkRegion(CODIE_LEFT, CODIE_RIGHT, { escapeUnicode: false });
|
|
229
|
+
const deficit = Buffer.byteLength(exports.STOCK_WORDMARK) - Buffer.byteLength(unpadded);
|
|
230
|
+
if (deficit < 0)
|
|
231
|
+
throw new Error("Codiedev wordmark is larger than the stock region");
|
|
232
|
+
const region = `${unpadded.slice(0, -1)}${" ".repeat(deficit)};`;
|
|
233
|
+
if (Buffer.byteLength(region) !== Buffer.byteLength(exports.STOCK_WORDMARK)) {
|
|
234
|
+
throw new Error("Codiedev wordmark does not match the stock region byte length");
|
|
235
|
+
}
|
|
236
|
+
return region;
|
|
237
|
+
})();
|
|
238
|
+
exports.HIDDEN_WORDMARK = (() => {
|
|
239
|
+
const unpadded = wordmarkRegion([], [], { escapeUnicode: false });
|
|
240
|
+
const deficit = Buffer.byteLength(exports.STOCK_WORDMARK) - Buffer.byteLength(unpadded);
|
|
241
|
+
if (deficit < 0)
|
|
242
|
+
throw new Error("Hidden wordmark is larger than the stock region");
|
|
243
|
+
const region = `${unpadded.slice(0, -1)}${" ".repeat(deficit)};`;
|
|
244
|
+
if (Buffer.byteLength(region) !== Buffer.byteLength(exports.STOCK_WORDMARK)) {
|
|
245
|
+
throw new Error("Hidden wordmark does not match the stock region byte length");
|
|
246
|
+
}
|
|
247
|
+
return region;
|
|
248
|
+
})();
|
|
249
|
+
// Pure: locate the stock or an earlier Codiedev wordmark in an engine binary.
|
|
250
|
+
// Refuses to touch the file unless exactly one known region appears; an
|
|
251
|
+
// already-hidden or reshaped upstream bundle is left alone.
|
|
252
|
+
function patchBrandWordmark(binary) {
|
|
253
|
+
const stock = Buffer.from(exports.STOCK_WORDMARK);
|
|
254
|
+
const candidates = [stock, Buffer.from(exports.LEGACY_CODIEDEV_WORDMARK), Buffer.from(exports.CODIEDEV_WORDMARK)];
|
|
255
|
+
const matches = candidates.flatMap((candidate) => {
|
|
256
|
+
const offset = binary.indexOf(candidate);
|
|
257
|
+
if (offset === -1)
|
|
258
|
+
return [];
|
|
259
|
+
if (binary.indexOf(candidate, offset + 1) !== -1)
|
|
260
|
+
return [offset, offset];
|
|
261
|
+
return [offset];
|
|
262
|
+
});
|
|
263
|
+
if (matches.length !== 1) {
|
|
264
|
+
return { changed: false, offset: -1, stock, replacement: null };
|
|
265
|
+
}
|
|
266
|
+
return { changed: true, offset: matches[0], stock, replacement: Buffer.from(exports.HIDDEN_WORDMARK) };
|
|
267
|
+
}
|
|
268
|
+
function resolveCapturePlugin({ homeDir, existsSync, }) {
|
|
269
|
+
const capturePath = path.join(homeDir, ".config", "opencode", "plugins", "codiedev.js");
|
|
270
|
+
if (!existsSync(capturePath)) {
|
|
271
|
+
throw new Error("Codiedev session capture is not installed. Run `codiedev connect` first");
|
|
272
|
+
}
|
|
273
|
+
return (0, url_1.pathToFileURL)(capturePath).href;
|
|
274
|
+
}
|
|
275
|
+
async function fetchApprovedCatalog({ baseUrl, key, fetchImpl = fetch, }) {
|
|
276
|
+
const controller = new AbortController();
|
|
277
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
278
|
+
try {
|
|
279
|
+
const response = await fetchImpl(`${cleanBaseUrl(baseUrl)}/models`, {
|
|
280
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
281
|
+
signal: controller.signal,
|
|
282
|
+
});
|
|
283
|
+
// The `codie` bin ships to every connected machine, including companies
|
|
284
|
+
// whose gateway is still switched off. Those developers must get an
|
|
285
|
+
// answer, not an HTTP status.
|
|
286
|
+
if (response.status === 403) {
|
|
287
|
+
throw new Error("The Codie harness is not enabled for your company yet. Ask your Codiedev admin to turn it on — " +
|
|
288
|
+
"your session capture keeps working in the meantime.");
|
|
289
|
+
}
|
|
290
|
+
if (response.status === 401) {
|
|
291
|
+
throw new Error("Your Codiedev token was rejected. Run `codiedev connect` to relink this machine.");
|
|
292
|
+
}
|
|
293
|
+
if (!response.ok)
|
|
294
|
+
throw new Error(`model catalog returned ${response.status}`);
|
|
295
|
+
const payload = (await response.json());
|
|
296
|
+
if (!Array.isArray(payload?.data) || payload.data.length === 0) {
|
|
297
|
+
throw new Error("model catalog is empty");
|
|
298
|
+
}
|
|
299
|
+
return payload.data.map((model) => ({
|
|
300
|
+
id: bareModel(model.id),
|
|
301
|
+
name: typeof model.name === "string" && model.name ? model.name : DEFAULT_NAMES[bareModel(model.id)],
|
|
302
|
+
}));
|
|
303
|
+
}
|
|
304
|
+
finally {
|
|
305
|
+
clearTimeout(timeout);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function parseCodieArgs(argv) {
|
|
309
|
+
const args = [];
|
|
310
|
+
const overrides = {};
|
|
311
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
312
|
+
const value = argv[index];
|
|
313
|
+
const next = argv[index + 1];
|
|
314
|
+
if (["--primary-model", "--subagent-model", "--plan-model"].includes(value)) {
|
|
315
|
+
if (!next)
|
|
316
|
+
throw new Error(`${value} requires a model`);
|
|
317
|
+
const envKey = value === "--primary-model"
|
|
318
|
+
? "CODIEDEV_PRIMARY_MODEL"
|
|
319
|
+
: value === "--subagent-model"
|
|
320
|
+
? "CODIEDEV_SUBAGENT_MODEL"
|
|
321
|
+
: "CODIEDEV_PLAN_MODEL";
|
|
322
|
+
overrides[envKey] = bareModel(next);
|
|
323
|
+
index += 1;
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
if ((value === "-m" || value === "--model") && next) {
|
|
327
|
+
args.push(value, `codiedev/${bareModel(next)}`);
|
|
328
|
+
index += 1;
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
if (value.startsWith("--model=")) {
|
|
332
|
+
args.push(`--model=codiedev/${bareModel(value.slice("--model=".length))}`);
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
args.push(value);
|
|
336
|
+
}
|
|
337
|
+
return { args, overrides };
|
|
338
|
+
}
|
|
339
|
+
function validateCodieArgs(args, catalog) {
|
|
340
|
+
const approved = new Set((catalog || []).map((model) => bareModel(model.id)).filter(Boolean));
|
|
341
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
342
|
+
const value = args[index];
|
|
343
|
+
const model = value === "-m" || value === "--model"
|
|
344
|
+
? args[index + 1]
|
|
345
|
+
: value.startsWith("--model=")
|
|
346
|
+
? value.slice("--model=".length)
|
|
347
|
+
: "";
|
|
348
|
+
if (model && !approved.has(bareModel(model))) {
|
|
349
|
+
throw new Error(`Requested model is not approved for this Codiedev account: ${bareModel(model)}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const OPENCODE_RUNTIME_SPEC = "opencode-ai@1.18.4";
|
|
2
|
+
export declare const CODIE_MIN_NODE_MAJOR = 20;
|
|
3
|
+
export type CodieRuntime = {
|
|
4
|
+
kind: "ready";
|
|
5
|
+
binary: string;
|
|
6
|
+
} | {
|
|
7
|
+
kind: "missing";
|
|
8
|
+
installDir: string;
|
|
9
|
+
binary: string;
|
|
10
|
+
spec: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function codieRuntimeDir(stateDir: string): string;
|
|
13
|
+
export declare function resolveCodieRuntime({ env, stateDir, existsSync, resolveBundled, }: {
|
|
14
|
+
env: Record<string, string | undefined>;
|
|
15
|
+
stateDir: string;
|
|
16
|
+
existsSync: (candidate: string) => boolean;
|
|
17
|
+
resolveBundled: () => string | null;
|
|
18
|
+
}): CodieRuntime;
|
|
19
|
+
export declare function assertCodieNodeVersion(version: string): void;
|