oh-my-opencode-slim 2.0.2 → 2.0.3
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 +5 -1
- package/README.zh-CN.md +1 -0
- package/dist/cli/companion.d.ts +2 -2
- package/dist/cli/index.js +308 -59
- package/dist/companion/manager.d.ts +1 -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 +1386 -463
- 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,9 @@ 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
|
+
</tr>
|
|
750
754
|
</tbody>
|
|
751
755
|
</table>
|
|
752
756
|
|
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,12 +749,7 @@ 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
|
}
|
|
@@ -1267,6 +1277,7 @@ function mergePluginConfigs(base, override) {
|
|
|
1267
1277
|
backgroundJobs: deepMerge(base.backgroundJobs, override.backgroundJobs),
|
|
1268
1278
|
fallback: deepMerge(base.fallback, override.fallback),
|
|
1269
1279
|
council: deepMerge(base.council, override.council),
|
|
1280
|
+
acpAgents: deepMerge(base.acpAgents, override.acpAgents),
|
|
1270
1281
|
companion: deepMerge(base.companion, override.companion)
|
|
1271
1282
|
};
|
|
1272
1283
|
}
|
|
@@ -1582,28 +1593,75 @@ function writeBackgroundSubagentsBlock(targetPath) {
|
|
|
1582
1593
|
writeFileSync2(targetPath, nextContent);
|
|
1583
1594
|
}
|
|
1584
1595
|
|
|
1585
|
-
// src/
|
|
1596
|
+
// src/companion/updater.ts
|
|
1597
|
+
init_compat();
|
|
1598
|
+
import { createHash } from "node:crypto";
|
|
1586
1599
|
import {
|
|
1587
1600
|
chmodSync,
|
|
1588
1601
|
copyFileSync as copyFileSync3,
|
|
1589
1602
|
existsSync as existsSync6,
|
|
1590
1603
|
mkdirSync as mkdirSync5,
|
|
1591
1604
|
mkdtempSync,
|
|
1605
|
+
readFileSync as readFileSync5,
|
|
1592
1606
|
renameSync as renameSync2,
|
|
1593
1607
|
rmSync as rmSync2,
|
|
1608
|
+
statSync as statSync4,
|
|
1594
1609
|
writeFileSync as writeFileSync3
|
|
1595
1610
|
} from "node:fs";
|
|
1596
|
-
import { homedir as homedir4, tmpdir } from "node:os";
|
|
1611
|
+
import { homedir as homedir4, platform, tmpdir } from "node:os";
|
|
1597
1612
|
import * as path2 from "node:path";
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1613
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
1614
|
+
|
|
1615
|
+
// src/utils/logger.ts
|
|
1616
|
+
import { appendFile } from "node:fs/promises";
|
|
1617
|
+
var RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
|
|
1618
|
+
var logFile = null;
|
|
1619
|
+
var writeChain = Promise.resolve();
|
|
1620
|
+
function log(message, data) {
|
|
1621
|
+
const target = logFile;
|
|
1622
|
+
if (!target)
|
|
1623
|
+
return;
|
|
1624
|
+
try {
|
|
1625
|
+
const timestamp = new Date().toISOString();
|
|
1626
|
+
let dataStr = "";
|
|
1627
|
+
if (data !== undefined) {
|
|
1628
|
+
try {
|
|
1629
|
+
dataStr = JSON.stringify(data);
|
|
1630
|
+
} catch {
|
|
1631
|
+
dataStr = "[unserializable]";
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
const logEntry = `[${timestamp}] ${message} ${dataStr}
|
|
1635
|
+
`;
|
|
1636
|
+
writeChain = writeChain.then(() => appendFile(target, logEntry)).catch(() => {});
|
|
1637
|
+
} catch {}
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
// src/companion/updater.ts
|
|
1641
|
+
var DOWNLOAD_TIMEOUT_MS = 30000;
|
|
1642
|
+
var LOCK_TIMEOUT_MS = 2000;
|
|
1643
|
+
var STALE_LOCK_MS = 5 * 60000;
|
|
1644
|
+
var FIRST_METADATA_VERSION = "0.1.2";
|
|
1645
|
+
var COMPANION_MANIFEST = {
|
|
1646
|
+
version: "0.1.3",
|
|
1647
|
+
tag: "companion-v0.1.3",
|
|
1648
|
+
repo: "alvinunreal/oh-my-opencode-slim",
|
|
1649
|
+
checksums: {
|
|
1650
|
+
"oh-my-opencode-slim-companion-v0.1.3-aarch64-apple-darwin.tar.gz": "b4885f9b1900c02376e5f8f5ae6f3b8a89d26f7514b03f836d7e3d618164a0ed",
|
|
1651
|
+
"oh-my-opencode-slim-companion-v0.1.3-aarch64-unknown-linux-gnu.tar.gz": "ed7cffc583e1eaa78c9bea702e6b6aa3bbc5bb4d881713fb2050237ba6b7aca5",
|
|
1652
|
+
"oh-my-opencode-slim-companion-v0.1.3-x86_64-apple-darwin.tar.gz": "98d8ea7c7bc4415b18e0d4c524adb4eb9a84c872919840fdc021f0f50c61f808",
|
|
1653
|
+
"oh-my-opencode-slim-companion-v0.1.3-x86_64-pc-windows-msvc.zip": "9316a49bf01f3b4fb1ce2d62edfc46094e73bb153d6ce023fb7df085afcf77bd",
|
|
1654
|
+
"oh-my-opencode-slim-companion-v0.1.3-x86_64-unknown-linux-gnu.tar.gz": "33f5fd4b6c80155a019391e5efb13904ca9531ba8dd8c6cba30a161f1b07b764"
|
|
1655
|
+
}
|
|
1656
|
+
};
|
|
1601
1657
|
function getCompanionTarget() {
|
|
1602
1658
|
const p = process.platform;
|
|
1603
1659
|
const a = process.arch;
|
|
1604
1660
|
if (p === "darwin") {
|
|
1605
1661
|
if (a === "arm64")
|
|
1606
1662
|
return "aarch64-apple-darwin";
|
|
1663
|
+
if (a === "x64")
|
|
1664
|
+
return "x86_64-apple-darwin";
|
|
1607
1665
|
} else if (p === "linux") {
|
|
1608
1666
|
if (a === "x64")
|
|
1609
1667
|
return "x86_64-unknown-linux-gnu";
|
|
@@ -1618,47 +1676,109 @@ function getCompanionTarget() {
|
|
|
1618
1676
|
function getCompanionBinaryPath() {
|
|
1619
1677
|
const xdg = process.env.XDG_DATA_HOME?.trim();
|
|
1620
1678
|
const base = xdg && path2.isAbsolute(xdg) ? xdg : path2.join(homedir4(), ".local", "share");
|
|
1621
|
-
return path2.join(base, "opencode", "storage", "oh-my-opencode-slim", "bin",
|
|
1679
|
+
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
1680
|
}
|
|
1623
|
-
async function
|
|
1681
|
+
async function ensureCompanionVersion(options) {
|
|
1682
|
+
const { config, dryRun = false } = options;
|
|
1683
|
+
const manifest = options.manifest ?? COMPANION_MANIFEST;
|
|
1684
|
+
const binaryPath = getCompanionBinaryPath();
|
|
1685
|
+
if (config?.enabled !== true) {
|
|
1686
|
+
return { status: "skipped", reason: "disabled", binaryPath };
|
|
1687
|
+
}
|
|
1688
|
+
if (config.binaryPath?.trim()) {
|
|
1689
|
+
return { status: "skipped", reason: "custom-binary", binaryPath };
|
|
1690
|
+
}
|
|
1624
1691
|
const target = getCompanionTarget();
|
|
1625
|
-
const finalBinaryPath = getCompanionBinaryPath();
|
|
1626
1692
|
if (!target) {
|
|
1627
1693
|
return {
|
|
1628
|
-
|
|
1629
|
-
|
|
1694
|
+
status: "failed",
|
|
1695
|
+
binaryPath,
|
|
1630
1696
|
error: `Unsupported platform/architecture: ${process.platform} ${process.arch}`
|
|
1631
1697
|
};
|
|
1632
1698
|
}
|
|
1699
|
+
const current = readInstallMetadata(binaryPath);
|
|
1700
|
+
if (existsSync6(binaryPath) && !current && manifest.version === FIRST_METADATA_VERSION) {
|
|
1701
|
+
const archiveName = companionArchiveName(manifest.version, target);
|
|
1702
|
+
writeInstallMetadata(binaryPath, {
|
|
1703
|
+
version: manifest.version,
|
|
1704
|
+
tag: manifest.tag,
|
|
1705
|
+
target,
|
|
1706
|
+
installedAt: new Date().toISOString(),
|
|
1707
|
+
archiveName,
|
|
1708
|
+
checksum: manifest.checksums?.[archiveName]
|
|
1709
|
+
});
|
|
1710
|
+
return { status: "current", binaryPath, version: manifest.version };
|
|
1711
|
+
}
|
|
1712
|
+
if (existsSync6(binaryPath) && current?.target === target && compareSemver(current.version, manifest.version) >= 0) {
|
|
1713
|
+
return { status: "current", binaryPath, version: current.version };
|
|
1714
|
+
}
|
|
1715
|
+
if (dryRun) {
|
|
1716
|
+
return { status: "installed", binaryPath, version: manifest.version };
|
|
1717
|
+
}
|
|
1718
|
+
return withCompanionInstallLock(binaryPath, options.lockTimeoutMs, options.lockStaleMs, async () => {
|
|
1719
|
+
const lockedCurrent = readInstallMetadata(binaryPath);
|
|
1720
|
+
if (existsSync6(binaryPath) && !lockedCurrent && manifest.version === FIRST_METADATA_VERSION) {
|
|
1721
|
+
const archiveName = companionArchiveName(manifest.version, target);
|
|
1722
|
+
writeInstallMetadata(binaryPath, {
|
|
1723
|
+
version: manifest.version,
|
|
1724
|
+
tag: manifest.tag,
|
|
1725
|
+
target,
|
|
1726
|
+
installedAt: new Date().toISOString(),
|
|
1727
|
+
archiveName,
|
|
1728
|
+
checksum: manifest.checksums?.[archiveName]
|
|
1729
|
+
});
|
|
1730
|
+
return { status: "current", binaryPath, version: manifest.version };
|
|
1731
|
+
}
|
|
1732
|
+
if (existsSync6(binaryPath) && lockedCurrent?.target === target && compareSemver(lockedCurrent.version, manifest.version) >= 0) {
|
|
1733
|
+
return {
|
|
1734
|
+
status: "current",
|
|
1735
|
+
binaryPath,
|
|
1736
|
+
version: lockedCurrent.version
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1739
|
+
return installCompanionArchive(binaryPath, target, manifest, options.downloadTimeoutMs ?? DOWNLOAD_TIMEOUT_MS);
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1742
|
+
async function installCompanionArchive(finalBinaryPath, target, manifest, downloadTimeoutMs) {
|
|
1633
1743
|
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}`);
|
|
1744
|
+
const archiveName = companionArchiveName(manifest.version, target, isWindows);
|
|
1745
|
+
const downloadUrl = `https://github.com/${manifest.repo}/releases/download/${manifest.tag}/${archiveName}`;
|
|
1746
|
+
const expectedChecksum = manifest.checksums?.[archiveName];
|
|
1747
|
+
if (!expectedChecksum) {
|
|
1641
1748
|
return {
|
|
1642
|
-
|
|
1643
|
-
|
|
1749
|
+
status: "failed",
|
|
1750
|
+
binaryPath: finalBinaryPath,
|
|
1751
|
+
error: `Missing SHA256 checksum for companion archive: ${archiveName}`
|
|
1644
1752
|
};
|
|
1645
1753
|
}
|
|
1646
1754
|
let buffer;
|
|
1755
|
+
const controller = new AbortController;
|
|
1756
|
+
const timeout = setTimeout(() => controller.abort(), downloadTimeoutMs);
|
|
1647
1757
|
try {
|
|
1648
|
-
const res = await fetch(downloadUrl);
|
|
1758
|
+
const res = await fetch(downloadUrl, { signal: controller.signal });
|
|
1649
1759
|
if (!res.ok) {
|
|
1650
1760
|
return {
|
|
1651
|
-
|
|
1652
|
-
|
|
1761
|
+
status: "failed",
|
|
1762
|
+
binaryPath: finalBinaryPath,
|
|
1653
1763
|
error: `Failed to download companion binary (HTTP ${res.status}): ${res.statusText}`
|
|
1654
1764
|
};
|
|
1655
1765
|
}
|
|
1656
1766
|
buffer = await res.arrayBuffer();
|
|
1657
1767
|
} catch (err) {
|
|
1658
1768
|
return {
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
error: `Failed to fetch companion archive: ${
|
|
1769
|
+
status: "failed",
|
|
1770
|
+
binaryPath: finalBinaryPath,
|
|
1771
|
+
error: `Failed to fetch companion archive: ${formatError(err)}`
|
|
1772
|
+
};
|
|
1773
|
+
} finally {
|
|
1774
|
+
clearTimeout(timeout);
|
|
1775
|
+
}
|
|
1776
|
+
const checksum = createHash("sha256").update(Buffer.from(buffer)).digest("hex");
|
|
1777
|
+
if (checksum !== expectedChecksum) {
|
|
1778
|
+
return {
|
|
1779
|
+
status: "failed",
|
|
1780
|
+
binaryPath: finalBinaryPath,
|
|
1781
|
+
error: "Companion archive checksum mismatch"
|
|
1662
1782
|
};
|
|
1663
1783
|
}
|
|
1664
1784
|
let tempDir = "";
|
|
@@ -1672,14 +1792,13 @@ async function installCompanion(config) {
|
|
|
1672
1792
|
const { extractZip: extractZip2 } = await Promise.resolve().then(() => (init_zip_extractor(), exports_zip_extractor));
|
|
1673
1793
|
await extractZip2(archivePath, extractedDir);
|
|
1674
1794
|
} else {
|
|
1675
|
-
const
|
|
1676
|
-
const proc = crossSpawn2(["tar", "-xzf", archivePath, "-C", extractedDir]);
|
|
1795
|
+
const proc = crossSpawn(["tar", "-xzf", archivePath, "-C", extractedDir]);
|
|
1677
1796
|
const exitCode = await proc.exited;
|
|
1678
1797
|
if (exitCode !== 0) {
|
|
1679
1798
|
const stderr = await proc.stderr();
|
|
1680
1799
|
return {
|
|
1681
|
-
|
|
1682
|
-
|
|
1800
|
+
status: "failed",
|
|
1801
|
+
binaryPath: finalBinaryPath,
|
|
1683
1802
|
error: `Archive extraction failed (tar exited with ${exitCode}): ${stderr}`
|
|
1684
1803
|
};
|
|
1685
1804
|
}
|
|
@@ -1688,8 +1807,8 @@ async function installCompanion(config) {
|
|
|
1688
1807
|
const extractedBinaryPath = path2.join(extractedDir, binaryName);
|
|
1689
1808
|
if (!existsSync6(extractedBinaryPath)) {
|
|
1690
1809
|
return {
|
|
1691
|
-
|
|
1692
|
-
|
|
1810
|
+
status: "failed",
|
|
1811
|
+
binaryPath: finalBinaryPath,
|
|
1693
1812
|
error: `Binary ${binaryName} not found in extracted archive`
|
|
1694
1813
|
};
|
|
1695
1814
|
}
|
|
@@ -1701,15 +1820,24 @@ async function installCompanion(config) {
|
|
|
1701
1820
|
chmodSync(tmpFinalPath, 493);
|
|
1702
1821
|
}
|
|
1703
1822
|
renameSync2(tmpFinalPath, finalBinaryPath);
|
|
1823
|
+
writeInstallMetadata(finalBinaryPath, {
|
|
1824
|
+
version: manifest.version,
|
|
1825
|
+
tag: manifest.tag,
|
|
1826
|
+
target,
|
|
1827
|
+
installedAt: new Date().toISOString(),
|
|
1828
|
+
archiveName,
|
|
1829
|
+
checksum
|
|
1830
|
+
});
|
|
1704
1831
|
return {
|
|
1705
|
-
|
|
1706
|
-
|
|
1832
|
+
status: "installed",
|
|
1833
|
+
binaryPath: finalBinaryPath,
|
|
1834
|
+
version: manifest.version
|
|
1707
1835
|
};
|
|
1708
1836
|
} catch (err) {
|
|
1709
1837
|
return {
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
error: `Failed to install companion: ${
|
|
1838
|
+
status: "failed",
|
|
1839
|
+
binaryPath: finalBinaryPath,
|
|
1840
|
+
error: `Failed to install companion: ${formatError(err)}`
|
|
1713
1841
|
};
|
|
1714
1842
|
} finally {
|
|
1715
1843
|
if (tempDir) {
|
|
@@ -1719,10 +1847,131 @@ async function installCompanion(config) {
|
|
|
1719
1847
|
}
|
|
1720
1848
|
}
|
|
1721
1849
|
}
|
|
1850
|
+
function readInstallMetadata(binaryPath) {
|
|
1851
|
+
try {
|
|
1852
|
+
const parsed = JSON.parse(readFileSync5(metadataPath(binaryPath), "utf8"));
|
|
1853
|
+
if (parsed?.version && parsed.tag && parsed.target) {
|
|
1854
|
+
return parsed;
|
|
1855
|
+
}
|
|
1856
|
+
} catch {}
|
|
1857
|
+
return null;
|
|
1858
|
+
}
|
|
1859
|
+
function writeInstallMetadata(binaryPath, metadata) {
|
|
1860
|
+
writeFileSync3(metadataPath(binaryPath), JSON.stringify(metadata, null, 2));
|
|
1861
|
+
}
|
|
1862
|
+
function metadataPath(binaryPath) {
|
|
1863
|
+
return `${binaryPath}.json`;
|
|
1864
|
+
}
|
|
1865
|
+
async function withCompanionInstallLock(binaryPath, timeoutMs, staleMs, run) {
|
|
1866
|
+
const lock = `${binaryPath}.lock`;
|
|
1867
|
+
const deadline = Date.now() + (timeoutMs ?? LOCK_TIMEOUT_MS);
|
|
1868
|
+
const staleAfterMs = staleMs ?? STALE_LOCK_MS;
|
|
1869
|
+
mkdirSync5(path2.dirname(binaryPath), { recursive: true });
|
|
1870
|
+
while (Date.now() <= deadline) {
|
|
1871
|
+
try {
|
|
1872
|
+
mkdirSync5(lock);
|
|
1873
|
+
try {
|
|
1874
|
+
return await run();
|
|
1875
|
+
} finally {
|
|
1876
|
+
try {
|
|
1877
|
+
rmSync2(lock, { recursive: true, force: true });
|
|
1878
|
+
} catch {}
|
|
1879
|
+
}
|
|
1880
|
+
} catch (err) {
|
|
1881
|
+
const code = err.code;
|
|
1882
|
+
if (code !== "EEXIST")
|
|
1883
|
+
throw err;
|
|
1884
|
+
try {
|
|
1885
|
+
const ageMs = Date.now() - statSync4(lock).mtimeMs;
|
|
1886
|
+
if (ageMs > staleAfterMs) {
|
|
1887
|
+
rmSync2(lock, { recursive: true, force: true });
|
|
1888
|
+
log("[companion] removed stale install lock", lock);
|
|
1889
|
+
continue;
|
|
1890
|
+
}
|
|
1891
|
+
} catch (statErr) {
|
|
1892
|
+
const statCode = statErr.code;
|
|
1893
|
+
if (statCode !== "ENOENT")
|
|
1894
|
+
throw statErr;
|
|
1895
|
+
}
|
|
1896
|
+
await delay(25);
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
log("[companion] install lock timed out", lock);
|
|
1900
|
+
return {
|
|
1901
|
+
status: "failed",
|
|
1902
|
+
binaryPath,
|
|
1903
|
+
error: "Timed out waiting for companion install lock"
|
|
1904
|
+
};
|
|
1905
|
+
}
|
|
1906
|
+
function companionArchiveName(version, target, isWindows = process.platform === "win32") {
|
|
1907
|
+
const ext = isWindows ? "zip" : "tar.gz";
|
|
1908
|
+
return `oh-my-opencode-slim-companion-v${version}-${target}.${ext}`;
|
|
1909
|
+
}
|
|
1910
|
+
function compareSemver(a, b) {
|
|
1911
|
+
const left = parseSemver(a);
|
|
1912
|
+
const right = parseSemver(b);
|
|
1913
|
+
if (!left || !right)
|
|
1914
|
+
return a.localeCompare(b);
|
|
1915
|
+
for (let i = 0;i < 3; i++) {
|
|
1916
|
+
const diff = left[i] - right[i];
|
|
1917
|
+
if (diff !== 0)
|
|
1918
|
+
return diff;
|
|
1919
|
+
}
|
|
1920
|
+
return 0;
|
|
1921
|
+
}
|
|
1922
|
+
function parseSemver(version) {
|
|
1923
|
+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
|
|
1924
|
+
if (!match)
|
|
1925
|
+
return null;
|
|
1926
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
1927
|
+
}
|
|
1928
|
+
function formatError(err) {
|
|
1929
|
+
return err instanceof Error ? err.message : String(err);
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
// src/cli/companion.ts
|
|
1933
|
+
async function installCompanion(config) {
|
|
1934
|
+
const target = getCompanionTarget();
|
|
1935
|
+
const finalBinaryPath = getCompanionBinaryPath();
|
|
1936
|
+
if (!target) {
|
|
1937
|
+
return {
|
|
1938
|
+
success: false,
|
|
1939
|
+
configPath: finalBinaryPath,
|
|
1940
|
+
error: `Unsupported platform/architecture: ${process.platform} ${process.arch}`
|
|
1941
|
+
};
|
|
1942
|
+
}
|
|
1943
|
+
const ext = process.platform === "win32" ? "zip" : "tar.gz";
|
|
1944
|
+
const archiveName = `oh-my-opencode-slim-companion-v${COMPANION_MANIFEST.version}-${target}.${ext}`;
|
|
1945
|
+
const downloadUrl = `https://github.com/${COMPANION_MANIFEST.repo}/releases/download/${COMPANION_MANIFEST.tag}/${archiveName}`;
|
|
1946
|
+
if (config.dryRun) {
|
|
1947
|
+
console.log(` [dry-run] Detected companion target: ${target}`);
|
|
1948
|
+
console.log(` [dry-run] Would download archive: ${downloadUrl}`);
|
|
1949
|
+
console.log(` [dry-run] Would extract and install to: ${finalBinaryPath}`);
|
|
1950
|
+
return {
|
|
1951
|
+
success: true,
|
|
1952
|
+
configPath: finalBinaryPath
|
|
1953
|
+
};
|
|
1954
|
+
}
|
|
1955
|
+
const result = await ensureCompanionVersion({
|
|
1956
|
+
config: { enabled: true },
|
|
1957
|
+
manifest: COMPANION_MANIFEST
|
|
1958
|
+
});
|
|
1959
|
+
if (result.status === "installed" || result.status === "current") {
|
|
1960
|
+
return {
|
|
1961
|
+
success: true,
|
|
1962
|
+
configPath: finalBinaryPath
|
|
1963
|
+
};
|
|
1964
|
+
}
|
|
1965
|
+
return {
|
|
1966
|
+
success: false,
|
|
1967
|
+
configPath: finalBinaryPath,
|
|
1968
|
+
error: result.status === "failed" ? result.error : `Companion install skipped: ${result.reason}`
|
|
1969
|
+
};
|
|
1970
|
+
}
|
|
1722
1971
|
// src/cli/system.ts
|
|
1723
1972
|
init_compat();
|
|
1724
1973
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
1725
|
-
import { statSync as
|
|
1974
|
+
import { statSync as statSync5 } from "node:fs";
|
|
1726
1975
|
var cachedOpenCodePath = null;
|
|
1727
1976
|
function resolvePathCommand(command) {
|
|
1728
1977
|
try {
|
|
@@ -1795,7 +2044,7 @@ function resolveOpenCodePath() {
|
|
|
1795
2044
|
if (opencodePath === "opencode")
|
|
1796
2045
|
continue;
|
|
1797
2046
|
try {
|
|
1798
|
-
const stat =
|
|
2047
|
+
const stat = statSync5(opencodePath);
|
|
1799
2048
|
if (stat.isFile()) {
|
|
1800
2049
|
cachedOpenCodePath = opencodePath;
|
|
1801
2050
|
return opencodePath;
|
|
@@ -1864,8 +2113,8 @@ var SYMBOLS = {
|
|
|
1864
2113
|
warn: `${YELLOW}[!]${RESET}`,
|
|
1865
2114
|
star: `${YELLOW}★${RESET}`
|
|
1866
2115
|
};
|
|
1867
|
-
var
|
|
1868
|
-
var GITHUB_URL = `https://github.com/${
|
|
2116
|
+
var GITHUB_REPO = "alvinunreal/oh-my-opencode-slim";
|
|
2117
|
+
var GITHUB_URL = `https://github.com/${GITHUB_REPO}`;
|
|
1869
2118
|
function printHeader(isUpdate) {
|
|
1870
2119
|
console.log();
|
|
1871
2120
|
console.log(`${BOLD}oh-my-opencode-slim ${isUpdate ? "Update" : "Install"}${RESET}`);
|
|
@@ -1908,7 +2157,7 @@ async function askToStarRepo(config) {
|
|
|
1908
2157
|
return;
|
|
1909
2158
|
try {
|
|
1910
2159
|
const { execFileSync } = await import("node:child_process");
|
|
1911
|
-
execFileSync("gh", ["api", "--silent", "--method", "PUT", `/user/starred/${
|
|
2160
|
+
execFileSync("gh", ["api", "--silent", "--method", "PUT", `/user/starred/${GITHUB_REPO}`], { stdio: "ignore", timeout: 1e4 });
|
|
1912
2161
|
printSuccess("Thanks for starring! ★");
|
|
1913
2162
|
} catch {
|
|
1914
2163
|
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
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { CompanionConfig } from '../config/schema';
|
|
2
|
+
export interface CompanionManifest {
|
|
3
|
+
version: string;
|
|
4
|
+
tag: string;
|
|
5
|
+
repo: string;
|
|
6
|
+
checksums?: Record<string, string>;
|
|
7
|
+
}
|
|
8
|
+
export type CompanionUpdateResult = {
|
|
9
|
+
status: 'installed';
|
|
10
|
+
binaryPath: string;
|
|
11
|
+
version: string;
|
|
12
|
+
} | {
|
|
13
|
+
status: 'current';
|
|
14
|
+
binaryPath: string;
|
|
15
|
+
version: string;
|
|
16
|
+
} | {
|
|
17
|
+
status: 'skipped';
|
|
18
|
+
reason: string;
|
|
19
|
+
binaryPath?: string;
|
|
20
|
+
} | {
|
|
21
|
+
status: 'failed';
|
|
22
|
+
error: string;
|
|
23
|
+
binaryPath: string;
|
|
24
|
+
};
|
|
25
|
+
export declare const COMPANION_MANIFEST: CompanionManifest;
|
|
26
|
+
export declare function getCompanionTarget(): string | null;
|
|
27
|
+
export declare function getCompanionBinaryPath(): string;
|
|
28
|
+
export declare function loadCompanionManifestFromPackageRoot(packageRoot: string): CompanionManifest | null;
|
|
29
|
+
export declare function ensureCompanionVersion(options: {
|
|
30
|
+
config?: CompanionConfig;
|
|
31
|
+
manifest?: CompanionManifest;
|
|
32
|
+
dryRun?: boolean;
|
|
33
|
+
downloadTimeoutMs?: number;
|
|
34
|
+
lockTimeoutMs?: number;
|
|
35
|
+
lockStaleMs?: number;
|
|
36
|
+
}): Promise<CompanionUpdateResult>;
|
package/dist/config/index.d.ts
CHANGED
|
@@ -2,4 +2,4 @@ export * from './constants';
|
|
|
2
2
|
export * from './council-schema';
|
|
3
3
|
export { deepMerge, loadAgentPrompt, loadPluginConfig, } from './loader';
|
|
4
4
|
export * from './schema';
|
|
5
|
-
export { getAgentOverride, getCustomAgentNames } from './utils';
|
|
5
|
+
export { getAcpAgentNames, getAgentOverride, getCustomAgentNames, } from './utils';
|