codiedev 0.9.26 → 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 +15 -3
- package/assets/opencode-plugin.mjs +54 -0
- package/assets/tui-brand-plugin.tsx +63 -0
- package/dist/backup/cache.js +2 -2
- package/dist/captureLog.js +6 -3
- package/dist/cli.js +7 -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/configDir.d.ts +1 -0
- package/dist/configDir.js +54 -0
- package/dist/connect.js +8 -2
- package/dist/hook.js +39 -41
- package/dist/upgradeNudge.js +6 -5
- package/dist/utils.d.ts +23 -3
- package/dist/utils.js +288 -20
- package/package.json +13 -5
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# codiedev
|
|
2
2
|
|
|
3
|
-
Connect Claude Code or
|
|
3
|
+
Connect Claude Code, Codex, Cursor, or OpenCode to Codiedev for team session capture.
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
@@ -12,8 +12,12 @@ The CLI will prompt you for an API token (get one at https://codiedev.com/portal
|
|
|
12
12
|
|
|
13
13
|
- **Claude Code** — `SessionEnd` hook in `~/.claude/settings.json`
|
|
14
14
|
- **Codex** — `Stop` hook in `~/.codex/hooks.json`
|
|
15
|
+
- **Cursor** — `sessionEnd` hook in `~/.cursor/hooks.json`
|
|
16
|
+
- **OpenCode / Codie** — plugin in `~/.config/opencode/plugins/codiedev.js`
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
Installed clients are wired automatically. The OpenCode adapter is staged even
|
|
19
|
+
when OpenCode is not installed yet, so adding the Codie harness later does not
|
|
20
|
+
require a second connect step.
|
|
17
21
|
|
|
18
22
|
## What gets captured
|
|
19
23
|
|
|
@@ -21,7 +25,9 @@ If both are installed, hooks are wired up for both. If neither is installed, the
|
|
|
21
25
|
- Transcript + basic stats (message count, tool calls)
|
|
22
26
|
- Processed into summaries and embeddings server-side for search in `/portal/memories`
|
|
23
27
|
|
|
24
|
-
Each captured memory records its
|
|
28
|
+
Each captured memory records its source (`claude-code`, `codex`, `cursor`, or
|
|
29
|
+
`opencode`) so the portal can label the client without breaking the shared
|
|
30
|
+
session → repository / branch / SHA → merge attribution model.
|
|
25
31
|
|
|
26
32
|
## Keep folders local
|
|
27
33
|
|
|
@@ -43,6 +49,12 @@ container needs its own rule unless `~/.codiedev/config.json` is persisted.
|
|
|
43
49
|
|
|
44
50
|
Codex doesn't currently expose a `SessionEnd` event, so the hook runs on `Stop` (fires after each assistant turn). The backend dedupes by `(company, session_id)` and replaces the stored transcript on re-upload so the latest turn's snapshot wins. Summarization happens once per session; subsequent turn uploads keep the raw transcript fresh but don't re-summarize (to avoid running up LLM cost).
|
|
45
51
|
|
|
52
|
+
## OpenCode note
|
|
53
|
+
|
|
54
|
+
OpenCode exposes session lifecycle events through plugins. Codiedev snapshots
|
|
55
|
+
the SDK message list on `session.idle`, normalizes it into the shared capture
|
|
56
|
+
pipeline, and lets the server close the session after a quiet-period lease.
|
|
57
|
+
|
|
46
58
|
## Config
|
|
47
59
|
|
|
48
60
|
Written to `~/.codiedev/config.json`. Override the backend with `CODIEDEV_URL` if needed.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Metadata-only attribution for the Codiedev OpenCode distribution.
|
|
2
|
+
// This plugin never reads or sends prompts, messages, code, diffs, or tool
|
|
3
|
+
// output. It only attaches stable session/git identifiers and explicit skill
|
|
4
|
+
// names to inference requests handled by the company gateway.
|
|
5
|
+
|
|
6
|
+
const skillUseBySession = new Map();
|
|
7
|
+
|
|
8
|
+
function clean(value, max) {
|
|
9
|
+
const text = typeof value === "string" ? value.trim() : "";
|
|
10
|
+
return text ? text.slice(0, max) : "";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function addSkill(sessionID, value) {
|
|
14
|
+
const skill = clean(value, 160).replace(/^\//, "").replace(/\.md$/i, "");
|
|
15
|
+
if (!skill) return;
|
|
16
|
+
const skills = skillUseBySession.get(sessionID) || new Set();
|
|
17
|
+
if (skills.size < 50) skills.add(skill);
|
|
18
|
+
skillUseBySession.set(sessionID, skills);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const CodiedevMetadata = async () => ({
|
|
22
|
+
"command.execute.before": async (input) => {
|
|
23
|
+
// Built-in commands are UI actions. Named custom commands are explicit
|
|
24
|
+
// skill dispatches and become part of the session attribution spine.
|
|
25
|
+
const builtIns = new Set([
|
|
26
|
+
"connect", "help", "models", "new", "share", "unshare", "undo", "redo",
|
|
27
|
+
"compact", "sessions", "timeline", "themes", "editor", "export", "exit",
|
|
28
|
+
]);
|
|
29
|
+
if (!builtIns.has(input.command)) addSkill(input.sessionID, input.command);
|
|
30
|
+
},
|
|
31
|
+
"tool.execute.before": async (input, output) => {
|
|
32
|
+
if (input.tool !== "skill") return;
|
|
33
|
+
addSkill(input.sessionID, output.args?.name || output.args?.skill || output.args?.key);
|
|
34
|
+
},
|
|
35
|
+
"chat.headers": async (input, output) => {
|
|
36
|
+
output.headers["X-Codiedev-Session-Id"] = clean(input.sessionID, 200);
|
|
37
|
+
const repo = clean(process.env.CODIEDEV_REPO_URL, 2048);
|
|
38
|
+
const branch = clean(process.env.CODIEDEV_BRANCH, 200);
|
|
39
|
+
const sha = clean(process.env.CODIEDEV_HEAD_SHA, 40);
|
|
40
|
+
const user = clean(process.env.CODIEDEV_USER, 200);
|
|
41
|
+
const skills = [...(skillUseBySession.get(input.sessionID) || [])].join(",");
|
|
42
|
+
if (repo) output.headers["X-Codiedev-Repo-Url"] = repo;
|
|
43
|
+
if (branch) output.headers["X-Codiedev-Branch"] = branch;
|
|
44
|
+
if (/^[0-9a-f]{7,40}$/i.test(sha)) output.headers["X-Codiedev-Head-Sha"] = sha;
|
|
45
|
+
if (user) output.headers["X-Codiedev-User"] = user;
|
|
46
|
+
if (skills) output.headers["X-Codiedev-Skills-Used"] = skills;
|
|
47
|
+
},
|
|
48
|
+
event: async ({ event }) => {
|
|
49
|
+
if (event?.type === "session.deleted") {
|
|
50
|
+
const sessionID = event.properties?.info?.id || event.properties?.sessionID;
|
|
51
|
+
if (sessionID) skillUseBySession.delete(sessionID);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** @jsxImportSource @opentui/solid */
|
|
2
|
+
// Codiedev branding for the OpenCode home screen: the intertwined c/d brand
|
|
3
|
+
// mark (rectangle crop, sized to the footprint the stock wordmark occupied)
|
|
4
|
+
// over the product name, with a "powered by opencode" credit line beneath.
|
|
5
|
+
// Installed and kept current by `codie`; deleting this file and its tui.json
|
|
6
|
+
// entry restores the stock logo.
|
|
7
|
+
// Mark ported from the retired Grok-fork TUI (assets/logo/logo07.txt), itself
|
|
8
|
+
// derived from vm-demo/public/codiedev-icon.png — keep aligned to that
|
|
9
|
+
// intertwined c/d silhouette when refreshing brand assets.
|
|
10
|
+
import type { TuiPlugin, TuiPluginModule } from "@opencode-ai/plugin/tui"
|
|
11
|
+
import { TextAttributes } from "@opentui/core"
|
|
12
|
+
|
|
13
|
+
const MARK = [
|
|
14
|
+
"⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿",
|
|
15
|
+
"⠀⠀⠀⢀⣀⣀⣀⡀⠀⠀⠀⠀⠀⣀⣀⣀⡀⣿⣿⣿",
|
|
16
|
+
"⠀⣠⣾⣿⣿⣿⣿⣿⣶⠄⢠⣶⣿⣿⣿⣿⣿⣿⣿⣿",
|
|
17
|
+
"⣼⣿⣿⠟⠉⠈⠉⠛⠁⣰⣿⣿⡟⠉⠁⠉⠻⣿⣿⣿",
|
|
18
|
+
"⣿⣿⣿⠀⠀⠀⠀⢀⣼⣿⣿⣿⡀⠀⠀⠀⠀⣿⣿⣿",
|
|
19
|
+
"⠹⣿⣿⣷⣤⣤⣴⣿⣿⡿⢿⣿⣷⣤⣤⣤⣾⣿⣿⠏",
|
|
20
|
+
"⠀⠈⠻⢿⣿⣿⣿⠿⠋⠀⠀⠙⠿⣿⣿⣿⡿⠟⠁",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
const tui: TuiPlugin = async (api) => {
|
|
24
|
+
api.slots.register({
|
|
25
|
+
slots: {
|
|
26
|
+
home_logo(ctx) {
|
|
27
|
+
const theme = ctx.theme.current
|
|
28
|
+
return (
|
|
29
|
+
<box flexDirection="column" alignItems="center" gap={1}>
|
|
30
|
+
<box flexDirection="column">
|
|
31
|
+
{MARK.map((line) => (
|
|
32
|
+
<text fg={theme.primary} selectable={false}>
|
|
33
|
+
{line}
|
|
34
|
+
</text>
|
|
35
|
+
))}
|
|
36
|
+
</box>
|
|
37
|
+
<box flexDirection="column" alignItems="center">
|
|
38
|
+
<box flexDirection="row">
|
|
39
|
+
<text fg={theme.text} attributes={TextAttributes.BOLD} selectable={false}>
|
|
40
|
+
codiedev
|
|
41
|
+
</text>
|
|
42
|
+
<text fg={theme.textMuted} selectable={false}>
|
|
43
|
+
{" "}
|
|
44
|
+
harness
|
|
45
|
+
</text>
|
|
46
|
+
</box>
|
|
47
|
+
<text fg={theme.textMuted} selectable={false}>
|
|
48
|
+
powered by opencode
|
|
49
|
+
</text>
|
|
50
|
+
</box>
|
|
51
|
+
</box>
|
|
52
|
+
)
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const plugin: TuiPluginModule & { id: string } = {
|
|
59
|
+
id: "codiedev.brand",
|
|
60
|
+
tui,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default plugin
|
package/dist/backup/cache.js
CHANGED
|
@@ -38,9 +38,9 @@ exports.loadCache = loadCache;
|
|
|
38
38
|
exports.saveCache = saveCache;
|
|
39
39
|
exports.applyCache = applyCache;
|
|
40
40
|
const fs = __importStar(require("fs"));
|
|
41
|
-
const os = __importStar(require("os"));
|
|
42
41
|
const path = __importStar(require("path"));
|
|
43
|
-
const
|
|
42
|
+
const configDir_1 = require("../configDir");
|
|
43
|
+
const CACHE_PATH = path.join((0, configDir_1.codiedevStateDir)(), "backup-state.json");
|
|
44
44
|
function cacheKey(repoKey, rel) {
|
|
45
45
|
return `${repoKey}::${rel}`;
|
|
46
46
|
}
|
package/dist/captureLog.js
CHANGED
|
@@ -36,7 +36,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.CAPTURE_LOG_PATH = exports.CAPTURE_LOG_MAX_BYTES = void 0;
|
|
37
37
|
exports.appendCaptureLog = appendCaptureLog;
|
|
38
38
|
exports.readCaptureLogTail = readCaptureLogTail;
|
|
39
|
-
// Rotating capture diagnostic log at
|
|
39
|
+
// Rotating capture diagnostic log at <codiedevStateDir()>/capture.log
|
|
40
|
+
// (~/.codiedev by default; ~/.codiedev-desktop under CODIEDEV_CONFIG_DIR).
|
|
40
41
|
//
|
|
41
42
|
// Every stage of the capture pipeline (capture-finish child, upload child,
|
|
42
43
|
// stderr from detached children) writes one line here so users — and
|
|
@@ -45,12 +46,14 @@ exports.readCaptureLogTail = readCaptureLogTail;
|
|
|
45
46
|
// best-effort throughout: appendCaptureLog NEVER throws into the hook path.
|
|
46
47
|
const fs = __importStar(require("fs"));
|
|
47
48
|
const path = __importStar(require("path"));
|
|
48
|
-
|
|
49
|
+
// Imported from configDir (not utils) — utils.ts imports this module, so
|
|
50
|
+
// importing utils.ts here would create a circular import.
|
|
51
|
+
const configDir_1 = require("./configDir");
|
|
49
52
|
// Rotate the log when it exceeds this size (keeps the newest ~half). 256 KB
|
|
50
53
|
// holds thousands of entries; a user would have to see hundreds of errors
|
|
51
54
|
// before hitting this limit, so it's a generous safety valve.
|
|
52
55
|
exports.CAPTURE_LOG_MAX_BYTES = 256 * 1024;
|
|
53
|
-
exports.CAPTURE_LOG_PATH = path.join(
|
|
56
|
+
exports.CAPTURE_LOG_PATH = path.join((0, configDir_1.codiedevStateDir)(), "capture.log");
|
|
54
57
|
// Append one structured line to the capture log. Rotates the file when it
|
|
55
58
|
// grows past CAPTURE_LOG_MAX_BYTES by truncating it to the newest half (keeps
|
|
56
59
|
// the most recent diagnostics). Best-effort: NEVER throws into the hook path —
|
package/dist/cli.js
CHANGED
|
@@ -46,6 +46,13 @@ Connect:
|
|
|
46
46
|
codiedev connect Link Claude Code / Codex to your org
|
|
47
47
|
codiedev doctor Verify your setup after connecting
|
|
48
48
|
|
|
49
|
+
Harness:
|
|
50
|
+
codie Launch the governed Codie harness.
|
|
51
|
+
Same install, same token as above —
|
|
52
|
+
use your own agent (Claude Code,
|
|
53
|
+
Codex, Cursor) or Codie, your call.
|
|
54
|
+
The engine downloads on first run.
|
|
55
|
+
|
|
49
56
|
Artifacts:
|
|
50
57
|
codiedev push <file.md> Author or update an artifact
|
|
51
58
|
codiedev pull <key> [--version N] [--out path]
|
package/dist/codie.d.ts
ADDED
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;
|