oh-my-opencode-slim 2.0.2 → 2.0.4
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.ja-JP.md +1 -0
- package/README.ko-KR.md +1 -0
- package/README.md +7 -1
- package/README.zh-CN.md +1 -0
- package/dist/cli/companion.d.ts +2 -2
- package/dist/cli/index.js +331 -68
- package/dist/companion/manager.d.ts +2 -0
- package/dist/companion/updater.d.ts +36 -0
- package/dist/config/index.d.ts +1 -1
- package/dist/config/schema.d.ts +76 -0
- package/dist/config/utils.d.ts +1 -0
- package/dist/hooks/auto-update-checker/types.d.ts +2 -0
- package/dist/index.js +1405 -481
- package/dist/tools/acp-run.d.ts +3 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/smartfetch/secondary-model.d.ts +7 -0
- package/dist/tui.js +98 -51
- package/oh-my-opencode-slim.schema.json +101 -0
- package/package.json +2 -1
- package/src/companion/companion-manifest.json +12 -0
package/README.ja-JP.md
CHANGED
|
@@ -595,6 +595,7 @@ Worktrees は、Git worktree を `.slim/worktrees/<slug>/` 配下の安全で隔
|
|
|
595
595
|
|-----|----------------|
|
|
596
596
|
| **[Council](docs/council.md)** | 複数のモデルを並列実行し、`@council` で 1 つの回答に統合します |
|
|
597
597
|
| **[Custom Agents](docs/configuration.md#custom-agents)** | カスタムプロンプト、モデル、MCP アクセス、Orchestrator の委譲ルールを備えた独自の専門エージェントを定義します |
|
|
598
|
+
| **[ACP Agents](docs/acp-agents.md)** | Claude Code ACP や Gemini ACP などの外部 ACP 互換エージェントを委譲可能なサブエージェントとして接続します |
|
|
598
599
|
| **[Multiplexer Integration](docs/multiplexer-integration.md)** | エージェントの動作を Tmux や Zellij のペインでライブ表示します |
|
|
599
600
|
| **[Codemap](docs/codemap.md)** | 階層的なコードマップを生成し、大規模コードベースを迅速に理解します |
|
|
600
601
|
| **[Clonedeps](docs/clonedeps.md)** | 選択した依存関係のソースを ignore 済みのローカルワークスペースにクローンし、調査できるようにします |
|
package/README.ko-KR.md
CHANGED
|
@@ -593,6 +593,7 @@ Worktrees는 Git worktree를 `.slim/worktrees/<slug>/` 아래의 안전하고
|
|
|
593
593
|
|-----|------|
|
|
594
594
|
| **[Council](docs/council.md)** | `@council`로 여러 모델을 병렬 실행하고 하나의 답변으로 종합 |
|
|
595
595
|
| **[Custom Agents](docs/configuration.md#custom-agents)** | 커스텀 프롬프트, 모델, MCP 접근, Orchestrator 위임 규칙으로 커스텀 전문 에이전트 정의 |
|
|
596
|
+
| **[ACP Agents](docs/acp-agents.md)** | Claude Code ACP 또는 Gemini ACP 같은 외부 ACP 호환 에이전트를 위임 가능한 서브에이전트로 연결 |
|
|
596
597
|
| **[Multiplexer Integration](docs/multiplexer-integration.md)** | Tmux 또는 Zellij 페인에서 에이전트 작업을 실시간으로 확인 |
|
|
597
598
|
| **[Codemap](docs/codemap.md)** | 계층형 코드맵을 생성하여 대규모 코드베이스를 빠르게 파악 |
|
|
598
599
|
| **[Clonedeps](docs/clonedeps.md)** | 선택한 의존성 소스를 무시된 로컬 워크스페이스에 복제하여 검사 |
|
package/README.md
CHANGED
|
@@ -619,6 +619,7 @@ Use this section as a map: start with installation, then jump to features, confi
|
|
|
619
619
|
|-----|----------------|
|
|
620
620
|
| **[Council](docs/council.md)** | Run multiple models in parallel and synthesize a single answer with `@council` |
|
|
621
621
|
| **[Custom Agents](docs/configuration.md#custom-agents)** | Define your own specialists with custom prompts, models, MCP access, and Orchestrator delegation rules |
|
|
622
|
+
| **[ACP Agents](docs/acp-agents.md)** | Connect external ACP-compatible agents such as Claude Code ACP or Gemini ACP as delegatable subagents |
|
|
622
623
|
| **[Multiplexer Integration](docs/multiplexer-integration.md)** | Watch agents work live in Tmux or Zellij panes |
|
|
623
624
|
| **[Codemap](docs/codemap.md)** | Generate hierarchical codemaps to understand large codebases faster |
|
|
624
625
|
| **[Clonedeps](docs/clonedeps.md)** | Clone selected dependency source into an ignored local workspace for inspection |
|
|
@@ -656,7 +657,7 @@ Use this section as a map: start with installation, then jump to features, confi
|
|
|
656
657
|
<p><sub>Every merged contribution leaves a mark on the realm.</sub></p>
|
|
657
658
|
|
|
658
659
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
|
659
|
-
[](#contributors-)
|
|
660
661
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
|
661
662
|
</div>
|
|
662
663
|
|
|
@@ -747,6 +748,11 @@ Use this section as a map: start with installation, then jump to features, confi
|
|
|
747
748
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/smatheusblu"><img src="https://avatars.githubusercontent.com/u/5666794?v=4?s=100" width="100px;" alt="Matheus Nogueira Silveira"/><br /><sub><b>Matheus Nogueira Silveira</b></sub></a><br /><a href="https://github.com/alvinunreal/oh-my-opencode-slim/commits?author=smatheusblu" title="Code">💻</a></td>
|
|
748
749
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/sktr"><img src="https://avatars.githubusercontent.com/u/44969514?v=4?s=100" width="100px;" alt="sktr"/><br /><sub><b>sktr</b></sub></a><br /><a href="https://github.com/alvinunreal/oh-my-opencode-slim/commits?author=sktr" title="Code">💻</a></td>
|
|
749
750
|
</tr>
|
|
751
|
+
<tr>
|
|
752
|
+
<td align="center" valign="top" width="16.66%"><a href="https://github.com/bobbyunknown"><img src="https://avatars.githubusercontent.com/u/62272380?v=4?s=100" width="100px;" alt="Insomnia"/><br /><sub><b>Insomnia</b></sub></a><br /><a href="https://github.com/alvinunreal/oh-my-opencode-slim/commits?author=bobbyunknown" title="Code">💻</a></td>
|
|
753
|
+
<td align="center" valign="top" width="16.66%"><a href="https://github.com/andrescastane"><img src="https://avatars.githubusercontent.com/u/13487870?v=4?s=100" width="100px;" alt="Andres Castañeda"/><br /><sub><b>Andres Castañeda</b></sub></a><br /><a href="https://github.com/alvinunreal/oh-my-opencode-slim/commits?author=andrescastane" title="Code">💻</a></td>
|
|
754
|
+
<td align="center" valign="top" width="16.66%"><a href="https://zaradacht.com/"><img src="https://avatars.githubusercontent.com/u/24251016?v=4?s=100" width="100px;" alt="Zaradacht Taifour (Zack)"/><br /><sub><b>Zaradacht Taifour (Zack)</b></sub></a><br /><a href="https://github.com/alvinunreal/oh-my-opencode-slim/commits?author=Zaradacht" title="Code">💻</a></td>
|
|
755
|
+
</tr>
|
|
750
756
|
</tbody>
|
|
751
757
|
</table>
|
|
752
758
|
|
package/README.zh-CN.md
CHANGED
|
@@ -590,6 +590,7 @@ Worktrees 将 Git worktree 作为安全、隔离的编码通道管理,默认
|
|
|
590
590
|
|-----|----------------|
|
|
591
591
|
| **[Council](docs/council.md)** | 使用 `@council` 并行运行多个模型并合成单一答案 |
|
|
592
592
|
| **[自定义智能体](docs/configuration.md#custom-agents)** | 使用自定义提示词、模型、MCP 访问和 Orchestrator 委派规则定义自己的专家 |
|
|
593
|
+
| **[ACP Agents](docs/acp-agents.md)** | 将 Claude Code ACP 或 Gemini ACP 等外部 ACP 兼容智能体连接为可委派子智能体 |
|
|
593
594
|
| **[多路复用器集成](docs/multiplexer-integration.md)** | 在 Tmux 或 Zellij 窗格中实时观看智能体工作 |
|
|
594
595
|
| **[Codemap](docs/codemap.md)** | 生成层级代码地图,更快理解大型代码库 |
|
|
595
596
|
| **[Clonedeps](docs/clonedeps.md)** | 将选定的依赖源码克隆到被忽略的本地工作区中以供检查 |
|
package/dist/cli/companion.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
+
import { getCompanionBinaryPath, getCompanionTarget } from '../companion/updater';
|
|
1
2
|
import type { ConfigMergeResult, InstallConfig } from './types';
|
|
2
|
-
export
|
|
3
|
-
export declare function getCompanionBinaryPath(): string;
|
|
3
|
+
export { getCompanionBinaryPath, getCompanionTarget };
|
|
4
4
|
export declare function installCompanion(config: InstallConfig): Promise<ConfigMergeResult>;
|
package/dist/cli/index.js
CHANGED
|
@@ -19,13 +19,7 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
|
19
19
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
20
20
|
|
|
21
21
|
// src/utils/compat.ts
|
|
22
|
-
var exports_compat = {};
|
|
23
|
-
__export(exports_compat, {
|
|
24
|
-
crossWrite: () => crossWrite,
|
|
25
|
-
crossSpawn: () => crossSpawn
|
|
26
|
-
});
|
|
27
22
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
28
|
-
import { writeFile as fsWriteFile } from "node:fs/promises";
|
|
29
23
|
function collectStream(stream) {
|
|
30
24
|
if (!stream)
|
|
31
25
|
return () => Promise.resolve("");
|
|
@@ -68,9 +62,6 @@ function crossSpawn(command, options) {
|
|
|
68
62
|
}
|
|
69
63
|
};
|
|
70
64
|
}
|
|
71
|
-
async function crossWrite(path, data) {
|
|
72
|
-
await fsWriteFile(path, Buffer.from(data));
|
|
73
|
-
}
|
|
74
65
|
var init_compat = () => {};
|
|
75
66
|
|
|
76
67
|
// src/utils/zip-extractor.ts
|
|
@@ -465,9 +456,28 @@ var FailoverConfigSchema = z2.object({
|
|
|
465
456
|
}).strict();
|
|
466
457
|
var CompanionConfigSchema = z2.object({
|
|
467
458
|
enabled: z2.boolean().optional(),
|
|
459
|
+
binaryPath: z2.string().min(1).optional().describe("Path to a custom companion binary to launch."),
|
|
468
460
|
position: z2.enum(["bottom-right", "bottom-left", "top-right", "top-left"]).optional(),
|
|
469
|
-
size: z2.enum(["small", "medium", "large"]).optional()
|
|
461
|
+
size: z2.enum(["small", "medium", "large"]).optional(),
|
|
462
|
+
gifPack: z2.enum(["default"]).optional().describe("Bundled companion animation pack to use."),
|
|
463
|
+
loopStyle: z2.enum(["classic", "smooth"]).optional().describe("Companion animation playback style: classic loops or smooth ping-pong playback."),
|
|
464
|
+
speed: z2.number().min(0.25).max(4).optional().describe("Companion animation playback speed multiplier. Defaults to 1."),
|
|
465
|
+
debug: z2.boolean().optional().describe("Enable verbose native companion debug logs.")
|
|
470
466
|
});
|
|
467
|
+
var AcpAgentPermissionModeSchema = z2.enum(["ask", "allow", "reject"]);
|
|
468
|
+
var AcpAgentConfigSchema = z2.object({
|
|
469
|
+
command: z2.string().min(1),
|
|
470
|
+
args: z2.array(z2.string()).default([]),
|
|
471
|
+
env: z2.record(z2.string(), z2.string()).default({}),
|
|
472
|
+
cwd: z2.string().min(1).optional(),
|
|
473
|
+
description: z2.string().min(1).optional(),
|
|
474
|
+
prompt: z2.string().min(1).optional(),
|
|
475
|
+
orchestratorPrompt: z2.string().min(1).optional(),
|
|
476
|
+
wrapperModel: ProviderModelIdSchema.optional(),
|
|
477
|
+
timeoutMs: z2.number().int().min(1000).max(900000).default(300000),
|
|
478
|
+
permissionMode: AcpAgentPermissionModeSchema.default("ask")
|
|
479
|
+
}).strict();
|
|
480
|
+
var AcpAgentsConfigSchema = z2.record(z2.string(), AcpAgentConfigSchema);
|
|
471
481
|
function validateCustomOnlyPromptFields(overrides, ctx, pathPrefix) {
|
|
472
482
|
for (const [name, override] of Object.entries(overrides)) {
|
|
473
483
|
const isBuiltInOrAlias = ALL_AGENT_NAMES.includes(name) || AGENT_ALIASES[name] !== undefined;
|
|
@@ -505,7 +515,8 @@ var PluginConfigSchema = z2.object({
|
|
|
505
515
|
backgroundJobs: BackgroundJobsConfigSchema.optional(),
|
|
506
516
|
fallback: FailoverConfigSchema.optional(),
|
|
507
517
|
council: CouncilConfigSchema.optional(),
|
|
508
|
-
companion: CompanionConfigSchema.optional()
|
|
518
|
+
companion: CompanionConfigSchema.optional(),
|
|
519
|
+
acpAgents: AcpAgentsConfigSchema.optional()
|
|
509
520
|
}).superRefine((value, ctx) => {
|
|
510
521
|
if (value.agents) {
|
|
511
522
|
validateCustomOnlyPromptFields(value.agents, ctx, ["agents"]);
|
|
@@ -726,7 +737,11 @@ function generateLiteConfig(installConfig) {
|
|
|
726
737
|
config.companion = {
|
|
727
738
|
enabled: true,
|
|
728
739
|
position: "bottom-right",
|
|
729
|
-
size: "medium"
|
|
740
|
+
size: "medium",
|
|
741
|
+
gifPack: "default",
|
|
742
|
+
loopStyle: "classic",
|
|
743
|
+
speed: 1,
|
|
744
|
+
debug: false
|
|
730
745
|
};
|
|
731
746
|
}
|
|
732
747
|
return config;
|
|
@@ -734,15 +749,24 @@ function generateLiteConfig(installConfig) {
|
|
|
734
749
|
|
|
735
750
|
// src/cli/config-io.ts
|
|
736
751
|
var PACKAGE_NAME = "oh-my-opencode-slim";
|
|
737
|
-
var DEFAULT_OPENCODE_AGENTS_TO_DISABLE = [
|
|
738
|
-
"build",
|
|
739
|
-
"explore",
|
|
740
|
-
"general",
|
|
741
|
-
"plan"
|
|
742
|
-
];
|
|
752
|
+
var DEFAULT_OPENCODE_AGENTS_TO_DISABLE = ["explore", "general"];
|
|
743
753
|
function isString(value) {
|
|
744
754
|
return typeof value === "string";
|
|
745
755
|
}
|
|
756
|
+
function getModelIds(model) {
|
|
757
|
+
if (isString(model))
|
|
758
|
+
return [model];
|
|
759
|
+
if (!Array.isArray(model))
|
|
760
|
+
return [];
|
|
761
|
+
return model.flatMap((entry) => {
|
|
762
|
+
if (isString(entry))
|
|
763
|
+
return [entry];
|
|
764
|
+
if (entry && typeof entry === "object" && isString(entry.id)) {
|
|
765
|
+
return [entry.id];
|
|
766
|
+
}
|
|
767
|
+
return [];
|
|
768
|
+
});
|
|
769
|
+
}
|
|
746
770
|
function getPlugins(config) {
|
|
747
771
|
return Array.isArray(config.plugin) ? config.plugin : [];
|
|
748
772
|
}
|
|
@@ -1207,17 +1231,17 @@ function detectCurrentConfig() {
|
|
|
1207
1231
|
const presetName = configObj.preset;
|
|
1208
1232
|
const presets = configObj.presets;
|
|
1209
1233
|
const agents = presets?.[presetName];
|
|
1210
|
-
if (agents) {
|
|
1211
|
-
const models = Object.values(agents).
|
|
1212
|
-
result.hasOpenAI
|
|
1213
|
-
result.hasAnthropic
|
|
1214
|
-
result.hasCopilot
|
|
1215
|
-
result.hasZaiPlan
|
|
1216
|
-
result.hasOpencodeZen
|
|
1217
|
-
if (models.some((m) => m
|
|
1234
|
+
if (agents && typeof agents === "object") {
|
|
1235
|
+
const models = Object.values(agents).filter((a) => a && typeof a === "object").flatMap((a) => getModelIds(a.model));
|
|
1236
|
+
result.hasOpenAI ||= models.some((m) => m.startsWith("openai/"));
|
|
1237
|
+
result.hasAnthropic ||= models.some((m) => m.startsWith("anthropic/"));
|
|
1238
|
+
result.hasCopilot ||= models.some((m) => m.startsWith("github-copilot/"));
|
|
1239
|
+
result.hasZaiPlan ||= models.some((m) => m.startsWith("zai-coding-plan/"));
|
|
1240
|
+
result.hasOpencodeZen ||= models.some((m) => m.startsWith("opencode/"));
|
|
1241
|
+
if (models.some((m) => m.startsWith("google/"))) {
|
|
1218
1242
|
result.hasAntigravity = true;
|
|
1219
1243
|
}
|
|
1220
|
-
if (models.some((m) => m
|
|
1244
|
+
if (models.some((m) => m.startsWith("chutes/"))) {
|
|
1221
1245
|
result.hasChutes = true;
|
|
1222
1246
|
}
|
|
1223
1247
|
}
|
|
@@ -1267,6 +1291,7 @@ function mergePluginConfigs(base, override) {
|
|
|
1267
1291
|
backgroundJobs: deepMerge(base.backgroundJobs, override.backgroundJobs),
|
|
1268
1292
|
fallback: deepMerge(base.fallback, override.fallback),
|
|
1269
1293
|
council: deepMerge(base.council, override.council),
|
|
1294
|
+
acpAgents: deepMerge(base.acpAgents, override.acpAgents),
|
|
1270
1295
|
companion: deepMerge(base.companion, override.companion)
|
|
1271
1296
|
};
|
|
1272
1297
|
}
|
|
@@ -1582,28 +1607,75 @@ function writeBackgroundSubagentsBlock(targetPath) {
|
|
|
1582
1607
|
writeFileSync2(targetPath, nextContent);
|
|
1583
1608
|
}
|
|
1584
1609
|
|
|
1585
|
-
// src/
|
|
1610
|
+
// src/companion/updater.ts
|
|
1611
|
+
init_compat();
|
|
1612
|
+
import { createHash } from "node:crypto";
|
|
1586
1613
|
import {
|
|
1587
1614
|
chmodSync,
|
|
1588
1615
|
copyFileSync as copyFileSync3,
|
|
1589
1616
|
existsSync as existsSync6,
|
|
1590
1617
|
mkdirSync as mkdirSync5,
|
|
1591
1618
|
mkdtempSync,
|
|
1619
|
+
readFileSync as readFileSync5,
|
|
1592
1620
|
renameSync as renameSync2,
|
|
1593
1621
|
rmSync as rmSync2,
|
|
1622
|
+
statSync as statSync4,
|
|
1594
1623
|
writeFileSync as writeFileSync3
|
|
1595
1624
|
} from "node:fs";
|
|
1596
|
-
import { homedir as homedir4, tmpdir } from "node:os";
|
|
1625
|
+
import { homedir as homedir4, platform, tmpdir } from "node:os";
|
|
1597
1626
|
import * as path2 from "node:path";
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1627
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
1628
|
+
|
|
1629
|
+
// src/utils/logger.ts
|
|
1630
|
+
import { appendFile } from "node:fs/promises";
|
|
1631
|
+
var RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
|
|
1632
|
+
var logFile = null;
|
|
1633
|
+
var writeChain = Promise.resolve();
|
|
1634
|
+
function log(message, data) {
|
|
1635
|
+
const target = logFile;
|
|
1636
|
+
if (!target)
|
|
1637
|
+
return;
|
|
1638
|
+
try {
|
|
1639
|
+
const timestamp = new Date().toISOString();
|
|
1640
|
+
let dataStr = "";
|
|
1641
|
+
if (data !== undefined) {
|
|
1642
|
+
try {
|
|
1643
|
+
dataStr = JSON.stringify(data);
|
|
1644
|
+
} catch {
|
|
1645
|
+
dataStr = "[unserializable]";
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
const logEntry = `[${timestamp}] ${message} ${dataStr}
|
|
1649
|
+
`;
|
|
1650
|
+
writeChain = writeChain.then(() => appendFile(target, logEntry)).catch(() => {});
|
|
1651
|
+
} catch {}
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
// src/companion/updater.ts
|
|
1655
|
+
var DOWNLOAD_TIMEOUT_MS = 30000;
|
|
1656
|
+
var LOCK_TIMEOUT_MS = 2000;
|
|
1657
|
+
var STALE_LOCK_MS = 5 * 60000;
|
|
1658
|
+
var FIRST_METADATA_VERSION = "0.1.2";
|
|
1659
|
+
var COMPANION_MANIFEST = {
|
|
1660
|
+
version: "0.1.3",
|
|
1661
|
+
tag: "companion-v0.1.3",
|
|
1662
|
+
repo: "alvinunreal/oh-my-opencode-slim",
|
|
1663
|
+
checksums: {
|
|
1664
|
+
"oh-my-opencode-slim-companion-v0.1.3-aarch64-apple-darwin.tar.gz": "b4885f9b1900c02376e5f8f5ae6f3b8a89d26f7514b03f836d7e3d618164a0ed",
|
|
1665
|
+
"oh-my-opencode-slim-companion-v0.1.3-aarch64-unknown-linux-gnu.tar.gz": "ed7cffc583e1eaa78c9bea702e6b6aa3bbc5bb4d881713fb2050237ba6b7aca5",
|
|
1666
|
+
"oh-my-opencode-slim-companion-v0.1.3-x86_64-apple-darwin.tar.gz": "98d8ea7c7bc4415b18e0d4c524adb4eb9a84c872919840fdc021f0f50c61f808",
|
|
1667
|
+
"oh-my-opencode-slim-companion-v0.1.3-x86_64-pc-windows-msvc.zip": "9316a49bf01f3b4fb1ce2d62edfc46094e73bb153d6ce023fb7df085afcf77bd",
|
|
1668
|
+
"oh-my-opencode-slim-companion-v0.1.3-x86_64-unknown-linux-gnu.tar.gz": "33f5fd4b6c80155a019391e5efb13904ca9531ba8dd8c6cba30a161f1b07b764"
|
|
1669
|
+
}
|
|
1670
|
+
};
|
|
1601
1671
|
function getCompanionTarget() {
|
|
1602
1672
|
const p = process.platform;
|
|
1603
1673
|
const a = process.arch;
|
|
1604
1674
|
if (p === "darwin") {
|
|
1605
1675
|
if (a === "arm64")
|
|
1606
1676
|
return "aarch64-apple-darwin";
|
|
1677
|
+
if (a === "x64")
|
|
1678
|
+
return "x86_64-apple-darwin";
|
|
1607
1679
|
} else if (p === "linux") {
|
|
1608
1680
|
if (a === "x64")
|
|
1609
1681
|
return "x86_64-unknown-linux-gnu";
|
|
@@ -1618,47 +1690,109 @@ function getCompanionTarget() {
|
|
|
1618
1690
|
function getCompanionBinaryPath() {
|
|
1619
1691
|
const xdg = process.env.XDG_DATA_HOME?.trim();
|
|
1620
1692
|
const base = xdg && path2.isAbsolute(xdg) ? xdg : path2.join(homedir4(), ".local", "share");
|
|
1621
|
-
return path2.join(base, "opencode", "storage", "oh-my-opencode-slim", "bin",
|
|
1693
|
+
return path2.join(base, "opencode", "storage", "oh-my-opencode-slim", "bin", platform() === "win32" ? "oh-my-opencode-slim-companion.exe" : "oh-my-opencode-slim-companion");
|
|
1622
1694
|
}
|
|
1623
|
-
async function
|
|
1695
|
+
async function ensureCompanionVersion(options) {
|
|
1696
|
+
const { config, dryRun = false } = options;
|
|
1697
|
+
const manifest = options.manifest ?? COMPANION_MANIFEST;
|
|
1698
|
+
const binaryPath = getCompanionBinaryPath();
|
|
1699
|
+
if (config?.enabled !== true) {
|
|
1700
|
+
return { status: "skipped", reason: "disabled", binaryPath };
|
|
1701
|
+
}
|
|
1702
|
+
if (config.binaryPath?.trim()) {
|
|
1703
|
+
return { status: "skipped", reason: "custom-binary", binaryPath };
|
|
1704
|
+
}
|
|
1624
1705
|
const target = getCompanionTarget();
|
|
1625
|
-
const finalBinaryPath = getCompanionBinaryPath();
|
|
1626
1706
|
if (!target) {
|
|
1627
1707
|
return {
|
|
1628
|
-
|
|
1629
|
-
|
|
1708
|
+
status: "failed",
|
|
1709
|
+
binaryPath,
|
|
1630
1710
|
error: `Unsupported platform/architecture: ${process.platform} ${process.arch}`
|
|
1631
1711
|
};
|
|
1632
1712
|
}
|
|
1713
|
+
const current = readInstallMetadata(binaryPath);
|
|
1714
|
+
if (existsSync6(binaryPath) && !current && manifest.version === FIRST_METADATA_VERSION) {
|
|
1715
|
+
const archiveName = companionArchiveName(manifest.version, target);
|
|
1716
|
+
writeInstallMetadata(binaryPath, {
|
|
1717
|
+
version: manifest.version,
|
|
1718
|
+
tag: manifest.tag,
|
|
1719
|
+
target,
|
|
1720
|
+
installedAt: new Date().toISOString(),
|
|
1721
|
+
archiveName,
|
|
1722
|
+
checksum: manifest.checksums?.[archiveName]
|
|
1723
|
+
});
|
|
1724
|
+
return { status: "current", binaryPath, version: manifest.version };
|
|
1725
|
+
}
|
|
1726
|
+
if (existsSync6(binaryPath) && current?.target === target && compareSemver(current.version, manifest.version) >= 0) {
|
|
1727
|
+
return { status: "current", binaryPath, version: current.version };
|
|
1728
|
+
}
|
|
1729
|
+
if (dryRun) {
|
|
1730
|
+
return { status: "installed", binaryPath, version: manifest.version };
|
|
1731
|
+
}
|
|
1732
|
+
return withCompanionInstallLock(binaryPath, options.lockTimeoutMs, options.lockStaleMs, async () => {
|
|
1733
|
+
const lockedCurrent = readInstallMetadata(binaryPath);
|
|
1734
|
+
if (existsSync6(binaryPath) && !lockedCurrent && manifest.version === FIRST_METADATA_VERSION) {
|
|
1735
|
+
const archiveName = companionArchiveName(manifest.version, target);
|
|
1736
|
+
writeInstallMetadata(binaryPath, {
|
|
1737
|
+
version: manifest.version,
|
|
1738
|
+
tag: manifest.tag,
|
|
1739
|
+
target,
|
|
1740
|
+
installedAt: new Date().toISOString(),
|
|
1741
|
+
archiveName,
|
|
1742
|
+
checksum: manifest.checksums?.[archiveName]
|
|
1743
|
+
});
|
|
1744
|
+
return { status: "current", binaryPath, version: manifest.version };
|
|
1745
|
+
}
|
|
1746
|
+
if (existsSync6(binaryPath) && lockedCurrent?.target === target && compareSemver(lockedCurrent.version, manifest.version) >= 0) {
|
|
1747
|
+
return {
|
|
1748
|
+
status: "current",
|
|
1749
|
+
binaryPath,
|
|
1750
|
+
version: lockedCurrent.version
|
|
1751
|
+
};
|
|
1752
|
+
}
|
|
1753
|
+
return installCompanionArchive(binaryPath, target, manifest, options.downloadTimeoutMs ?? DOWNLOAD_TIMEOUT_MS);
|
|
1754
|
+
});
|
|
1755
|
+
}
|
|
1756
|
+
async function installCompanionArchive(finalBinaryPath, target, manifest, downloadTimeoutMs) {
|
|
1633
1757
|
const isWindows = process.platform === "win32";
|
|
1634
|
-
const
|
|
1635
|
-
const
|
|
1636
|
-
const
|
|
1637
|
-
if (
|
|
1638
|
-
console.log(` [dry-run] Detected companion target: ${target}`);
|
|
1639
|
-
console.log(` [dry-run] Would download archive: ${downloadUrl}`);
|
|
1640
|
-
console.log(` [dry-run] Would extract and install to: ${finalBinaryPath}`);
|
|
1758
|
+
const archiveName = companionArchiveName(manifest.version, target, isWindows);
|
|
1759
|
+
const downloadUrl = `https://github.com/${manifest.repo}/releases/download/${manifest.tag}/${archiveName}`;
|
|
1760
|
+
const expectedChecksum = manifest.checksums?.[archiveName];
|
|
1761
|
+
if (!expectedChecksum) {
|
|
1641
1762
|
return {
|
|
1642
|
-
|
|
1643
|
-
|
|
1763
|
+
status: "failed",
|
|
1764
|
+
binaryPath: finalBinaryPath,
|
|
1765
|
+
error: `Missing SHA256 checksum for companion archive: ${archiveName}`
|
|
1644
1766
|
};
|
|
1645
1767
|
}
|
|
1646
1768
|
let buffer;
|
|
1769
|
+
const controller = new AbortController;
|
|
1770
|
+
const timeout = setTimeout(() => controller.abort(), downloadTimeoutMs);
|
|
1647
1771
|
try {
|
|
1648
|
-
const res = await fetch(downloadUrl);
|
|
1772
|
+
const res = await fetch(downloadUrl, { signal: controller.signal });
|
|
1649
1773
|
if (!res.ok) {
|
|
1650
1774
|
return {
|
|
1651
|
-
|
|
1652
|
-
|
|
1775
|
+
status: "failed",
|
|
1776
|
+
binaryPath: finalBinaryPath,
|
|
1653
1777
|
error: `Failed to download companion binary (HTTP ${res.status}): ${res.statusText}`
|
|
1654
1778
|
};
|
|
1655
1779
|
}
|
|
1656
1780
|
buffer = await res.arrayBuffer();
|
|
1657
1781
|
} catch (err) {
|
|
1658
1782
|
return {
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
error: `Failed to fetch companion archive: ${
|
|
1783
|
+
status: "failed",
|
|
1784
|
+
binaryPath: finalBinaryPath,
|
|
1785
|
+
error: `Failed to fetch companion archive: ${formatError(err)}`
|
|
1786
|
+
};
|
|
1787
|
+
} finally {
|
|
1788
|
+
clearTimeout(timeout);
|
|
1789
|
+
}
|
|
1790
|
+
const checksum = createHash("sha256").update(Buffer.from(buffer)).digest("hex");
|
|
1791
|
+
if (checksum !== expectedChecksum) {
|
|
1792
|
+
return {
|
|
1793
|
+
status: "failed",
|
|
1794
|
+
binaryPath: finalBinaryPath,
|
|
1795
|
+
error: "Companion archive checksum mismatch"
|
|
1662
1796
|
};
|
|
1663
1797
|
}
|
|
1664
1798
|
let tempDir = "";
|
|
@@ -1672,14 +1806,13 @@ async function installCompanion(config) {
|
|
|
1672
1806
|
const { extractZip: extractZip2 } = await Promise.resolve().then(() => (init_zip_extractor(), exports_zip_extractor));
|
|
1673
1807
|
await extractZip2(archivePath, extractedDir);
|
|
1674
1808
|
} else {
|
|
1675
|
-
const
|
|
1676
|
-
const proc = crossSpawn2(["tar", "-xzf", archivePath, "-C", extractedDir]);
|
|
1809
|
+
const proc = crossSpawn(["tar", "-xzf", archivePath, "-C", extractedDir]);
|
|
1677
1810
|
const exitCode = await proc.exited;
|
|
1678
1811
|
if (exitCode !== 0) {
|
|
1679
1812
|
const stderr = await proc.stderr();
|
|
1680
1813
|
return {
|
|
1681
|
-
|
|
1682
|
-
|
|
1814
|
+
status: "failed",
|
|
1815
|
+
binaryPath: finalBinaryPath,
|
|
1683
1816
|
error: `Archive extraction failed (tar exited with ${exitCode}): ${stderr}`
|
|
1684
1817
|
};
|
|
1685
1818
|
}
|
|
@@ -1688,8 +1821,8 @@ async function installCompanion(config) {
|
|
|
1688
1821
|
const extractedBinaryPath = path2.join(extractedDir, binaryName);
|
|
1689
1822
|
if (!existsSync6(extractedBinaryPath)) {
|
|
1690
1823
|
return {
|
|
1691
|
-
|
|
1692
|
-
|
|
1824
|
+
status: "failed",
|
|
1825
|
+
binaryPath: finalBinaryPath,
|
|
1693
1826
|
error: `Binary ${binaryName} not found in extracted archive`
|
|
1694
1827
|
};
|
|
1695
1828
|
}
|
|
@@ -1701,15 +1834,24 @@ async function installCompanion(config) {
|
|
|
1701
1834
|
chmodSync(tmpFinalPath, 493);
|
|
1702
1835
|
}
|
|
1703
1836
|
renameSync2(tmpFinalPath, finalBinaryPath);
|
|
1837
|
+
writeInstallMetadata(finalBinaryPath, {
|
|
1838
|
+
version: manifest.version,
|
|
1839
|
+
tag: manifest.tag,
|
|
1840
|
+
target,
|
|
1841
|
+
installedAt: new Date().toISOString(),
|
|
1842
|
+
archiveName,
|
|
1843
|
+
checksum
|
|
1844
|
+
});
|
|
1704
1845
|
return {
|
|
1705
|
-
|
|
1706
|
-
|
|
1846
|
+
status: "installed",
|
|
1847
|
+
binaryPath: finalBinaryPath,
|
|
1848
|
+
version: manifest.version
|
|
1707
1849
|
};
|
|
1708
1850
|
} catch (err) {
|
|
1709
1851
|
return {
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
error: `Failed to install companion: ${
|
|
1852
|
+
status: "failed",
|
|
1853
|
+
binaryPath: finalBinaryPath,
|
|
1854
|
+
error: `Failed to install companion: ${formatError(err)}`
|
|
1713
1855
|
};
|
|
1714
1856
|
} finally {
|
|
1715
1857
|
if (tempDir) {
|
|
@@ -1719,10 +1861,131 @@ async function installCompanion(config) {
|
|
|
1719
1861
|
}
|
|
1720
1862
|
}
|
|
1721
1863
|
}
|
|
1864
|
+
function readInstallMetadata(binaryPath) {
|
|
1865
|
+
try {
|
|
1866
|
+
const parsed = JSON.parse(readFileSync5(metadataPath(binaryPath), "utf8"));
|
|
1867
|
+
if (parsed?.version && parsed.tag && parsed.target) {
|
|
1868
|
+
return parsed;
|
|
1869
|
+
}
|
|
1870
|
+
} catch {}
|
|
1871
|
+
return null;
|
|
1872
|
+
}
|
|
1873
|
+
function writeInstallMetadata(binaryPath, metadata) {
|
|
1874
|
+
writeFileSync3(metadataPath(binaryPath), JSON.stringify(metadata, null, 2));
|
|
1875
|
+
}
|
|
1876
|
+
function metadataPath(binaryPath) {
|
|
1877
|
+
return `${binaryPath}.json`;
|
|
1878
|
+
}
|
|
1879
|
+
async function withCompanionInstallLock(binaryPath, timeoutMs, staleMs, run) {
|
|
1880
|
+
const lock = `${binaryPath}.lock`;
|
|
1881
|
+
const deadline = Date.now() + (timeoutMs ?? LOCK_TIMEOUT_MS);
|
|
1882
|
+
const staleAfterMs = staleMs ?? STALE_LOCK_MS;
|
|
1883
|
+
mkdirSync5(path2.dirname(binaryPath), { recursive: true });
|
|
1884
|
+
while (Date.now() <= deadline) {
|
|
1885
|
+
try {
|
|
1886
|
+
mkdirSync5(lock);
|
|
1887
|
+
try {
|
|
1888
|
+
return await run();
|
|
1889
|
+
} finally {
|
|
1890
|
+
try {
|
|
1891
|
+
rmSync2(lock, { recursive: true, force: true });
|
|
1892
|
+
} catch {}
|
|
1893
|
+
}
|
|
1894
|
+
} catch (err) {
|
|
1895
|
+
const code = err.code;
|
|
1896
|
+
if (code !== "EEXIST")
|
|
1897
|
+
throw err;
|
|
1898
|
+
try {
|
|
1899
|
+
const ageMs = Date.now() - statSync4(lock).mtimeMs;
|
|
1900
|
+
if (ageMs > staleAfterMs) {
|
|
1901
|
+
rmSync2(lock, { recursive: true, force: true });
|
|
1902
|
+
log("[companion] removed stale install lock", lock);
|
|
1903
|
+
continue;
|
|
1904
|
+
}
|
|
1905
|
+
} catch (statErr) {
|
|
1906
|
+
const statCode = statErr.code;
|
|
1907
|
+
if (statCode !== "ENOENT")
|
|
1908
|
+
throw statErr;
|
|
1909
|
+
}
|
|
1910
|
+
await delay(25);
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
log("[companion] install lock timed out", lock);
|
|
1914
|
+
return {
|
|
1915
|
+
status: "failed",
|
|
1916
|
+
binaryPath,
|
|
1917
|
+
error: "Timed out waiting for companion install lock"
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1920
|
+
function companionArchiveName(version, target, isWindows = process.platform === "win32") {
|
|
1921
|
+
const ext = isWindows ? "zip" : "tar.gz";
|
|
1922
|
+
return `oh-my-opencode-slim-companion-v${version}-${target}.${ext}`;
|
|
1923
|
+
}
|
|
1924
|
+
function compareSemver(a, b) {
|
|
1925
|
+
const left = parseSemver(a);
|
|
1926
|
+
const right = parseSemver(b);
|
|
1927
|
+
if (!left || !right)
|
|
1928
|
+
return a.localeCompare(b);
|
|
1929
|
+
for (let i = 0;i < 3; i++) {
|
|
1930
|
+
const diff = left[i] - right[i];
|
|
1931
|
+
if (diff !== 0)
|
|
1932
|
+
return diff;
|
|
1933
|
+
}
|
|
1934
|
+
return 0;
|
|
1935
|
+
}
|
|
1936
|
+
function parseSemver(version) {
|
|
1937
|
+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
|
|
1938
|
+
if (!match)
|
|
1939
|
+
return null;
|
|
1940
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
1941
|
+
}
|
|
1942
|
+
function formatError(err) {
|
|
1943
|
+
return err instanceof Error ? err.message : String(err);
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
// src/cli/companion.ts
|
|
1947
|
+
async function installCompanion(config) {
|
|
1948
|
+
const target = getCompanionTarget();
|
|
1949
|
+
const finalBinaryPath = getCompanionBinaryPath();
|
|
1950
|
+
if (!target) {
|
|
1951
|
+
return {
|
|
1952
|
+
success: false,
|
|
1953
|
+
configPath: finalBinaryPath,
|
|
1954
|
+
error: `Unsupported platform/architecture: ${process.platform} ${process.arch}`
|
|
1955
|
+
};
|
|
1956
|
+
}
|
|
1957
|
+
const ext = process.platform === "win32" ? "zip" : "tar.gz";
|
|
1958
|
+
const archiveName = `oh-my-opencode-slim-companion-v${COMPANION_MANIFEST.version}-${target}.${ext}`;
|
|
1959
|
+
const downloadUrl = `https://github.com/${COMPANION_MANIFEST.repo}/releases/download/${COMPANION_MANIFEST.tag}/${archiveName}`;
|
|
1960
|
+
if (config.dryRun) {
|
|
1961
|
+
console.log(` [dry-run] Detected companion target: ${target}`);
|
|
1962
|
+
console.log(` [dry-run] Would download archive: ${downloadUrl}`);
|
|
1963
|
+
console.log(` [dry-run] Would extract and install to: ${finalBinaryPath}`);
|
|
1964
|
+
return {
|
|
1965
|
+
success: true,
|
|
1966
|
+
configPath: finalBinaryPath
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
const result = await ensureCompanionVersion({
|
|
1970
|
+
config: { enabled: true },
|
|
1971
|
+
manifest: COMPANION_MANIFEST
|
|
1972
|
+
});
|
|
1973
|
+
if (result.status === "installed" || result.status === "current") {
|
|
1974
|
+
return {
|
|
1975
|
+
success: true,
|
|
1976
|
+
configPath: finalBinaryPath
|
|
1977
|
+
};
|
|
1978
|
+
}
|
|
1979
|
+
return {
|
|
1980
|
+
success: false,
|
|
1981
|
+
configPath: finalBinaryPath,
|
|
1982
|
+
error: result.status === "failed" ? result.error : `Companion install skipped: ${result.reason}`
|
|
1983
|
+
};
|
|
1984
|
+
}
|
|
1722
1985
|
// src/cli/system.ts
|
|
1723
1986
|
init_compat();
|
|
1724
1987
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
1725
|
-
import { statSync as
|
|
1988
|
+
import { statSync as statSync5 } from "node:fs";
|
|
1726
1989
|
var cachedOpenCodePath = null;
|
|
1727
1990
|
function resolvePathCommand(command) {
|
|
1728
1991
|
try {
|
|
@@ -1795,7 +2058,7 @@ function resolveOpenCodePath() {
|
|
|
1795
2058
|
if (opencodePath === "opencode")
|
|
1796
2059
|
continue;
|
|
1797
2060
|
try {
|
|
1798
|
-
const stat =
|
|
2061
|
+
const stat = statSync5(opencodePath);
|
|
1799
2062
|
if (stat.isFile()) {
|
|
1800
2063
|
cachedOpenCodePath = opencodePath;
|
|
1801
2064
|
return opencodePath;
|
|
@@ -1864,8 +2127,8 @@ var SYMBOLS = {
|
|
|
1864
2127
|
warn: `${YELLOW}[!]${RESET}`,
|
|
1865
2128
|
star: `${YELLOW}★${RESET}`
|
|
1866
2129
|
};
|
|
1867
|
-
var
|
|
1868
|
-
var GITHUB_URL = `https://github.com/${
|
|
2130
|
+
var GITHUB_REPO = "alvinunreal/oh-my-opencode-slim";
|
|
2131
|
+
var GITHUB_URL = `https://github.com/${GITHUB_REPO}`;
|
|
1869
2132
|
function printHeader(isUpdate) {
|
|
1870
2133
|
console.log();
|
|
1871
2134
|
console.log(`${BOLD}oh-my-opencode-slim ${isUpdate ? "Update" : "Install"}${RESET}`);
|
|
@@ -1908,7 +2171,7 @@ async function askToStarRepo(config) {
|
|
|
1908
2171
|
return;
|
|
1909
2172
|
try {
|
|
1910
2173
|
const { execFileSync } = await import("node:child_process");
|
|
1911
|
-
execFileSync("gh", ["api", "--silent", "--method", "PUT", `/user/starred/${
|
|
2174
|
+
execFileSync("gh", ["api", "--silent", "--method", "PUT", `/user/starred/${GITHUB_REPO}`], { stdio: "ignore", timeout: 1e4 });
|
|
1912
2175
|
printSuccess("Thanks for starring! ★");
|
|
1913
2176
|
} catch {
|
|
1914
2177
|
printInfo(`Couldn't star automatically. You can star manually:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CompanionConfig } from '../config/schema';
|
|
2
2
|
export declare function stateFilePath(): string;
|
|
3
|
+
export declare function resolveCompanionBinaryPath(config?: CompanionConfig): string | null;
|
|
3
4
|
/**
|
|
4
5
|
* Tracks live agent activity per session and mirrors it to the companion
|
|
5
6
|
* state file. Source of truth is OpenCode's session.status events: every
|
|
@@ -15,6 +16,7 @@ export declare class CompanionManager {
|
|
|
15
16
|
/** sessionId → agent name, for sessions currently busy. */
|
|
16
17
|
private readonly busyAgentSessions;
|
|
17
18
|
private readonly config?;
|
|
19
|
+
private companionProcess;
|
|
18
20
|
constructor(sessionId: string, cwd: string, config?: CompanionConfig);
|
|
19
21
|
onLoad(): void;
|
|
20
22
|
/**
|