oh-my-opencode-slim 2.0.1 → 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 +31 -1
- package/README.ko-KR.md +31 -1
- package/README.md +41 -2
- package/README.zh-CN.md +31 -1
- package/dist/agents/orchestrator.d.ts +0 -2
- package/dist/cli/companion.d.ts +2 -2
- package/dist/cli/index.js +326 -89
- package/dist/companion/manager.d.ts +1 -0
- package/dist/companion/updater.d.ts +36 -0
- package/dist/config/agent-mcps.d.ts +0 -4
- package/dist/config/constants.d.ts +1 -7
- package/dist/config/council-schema.d.ts +0 -15
- package/dist/config/index.d.ts +1 -1
- package/dist/config/runtime-preset.d.ts +0 -1
- package/dist/config/schema.d.ts +78 -68
- package/dist/config/utils.d.ts +1 -0
- package/dist/hooks/auto-update-checker/skill-sync.d.ts +9 -0
- package/dist/hooks/auto-update-checker/types.d.ts +2 -0
- package/dist/hooks/filter-available-skills/index.d.ts +1 -13
- package/dist/hooks/foreground-fallback/index.d.ts +1 -1
- package/dist/hooks/image-hook.d.ts +1 -13
- package/dist/hooks/index.d.ts +3 -2
- package/dist/hooks/phase-reminder/index.d.ts +10 -16
- package/dist/hooks/reflect/index.d.ts +13 -0
- package/dist/hooks/task-session-manager/index.d.ts +2 -16
- package/dist/hooks/types.d.ts +23 -0
- package/dist/index.js +1610 -585
- 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 +114 -76
- package/dist/utils/agent-variant.d.ts +0 -40
- package/dist/utils/compat.d.ts +0 -1
- package/dist/utils/guards.d.ts +4 -0
- package/dist/utils/index.d.ts +1 -2
- package/dist/utils/logger.d.ts +1 -1
- package/dist/utils/task.d.ts +0 -2
- package/oh-my-opencode-slim.schema.json +103 -249
- package/package.json +2 -1
- package/src/companion/companion-manifest.json +12 -0
- package/src/skills/codemap.md +4 -1
- package/src/skills/reflect/SKILL.md +193 -0
- package/src/skills/worktrees/SKILL.md +164 -0
- package/dist/config/fallback-chains.d.ts +0 -1
- package/dist/hooks/apply-patch/patch.d.ts +0 -2
- package/dist/hooks/delegate-task-retry/guidance.d.ts +0 -2
- package/dist/hooks/delegate-task-retry/index.d.ts +0 -4
- package/dist/hooks/json-error-recovery/index.d.ts +0 -1
- package/dist/utils/env.d.ts +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -19,14 +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
|
-
isBun: () => isBun,
|
|
25
|
-
crossWrite: () => crossWrite,
|
|
26
|
-
crossSpawn: () => crossSpawn
|
|
27
|
-
});
|
|
28
22
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
29
|
-
import { writeFile as fsWriteFile } from "node:fs/promises";
|
|
30
23
|
function collectStream(stream) {
|
|
31
24
|
if (!stream)
|
|
32
25
|
return () => Promise.resolve("");
|
|
@@ -69,13 +62,7 @@ function crossSpawn(command, options) {
|
|
|
69
62
|
}
|
|
70
63
|
};
|
|
71
64
|
}
|
|
72
|
-
|
|
73
|
-
await fsWriteFile(path, Buffer.from(data));
|
|
74
|
-
}
|
|
75
|
-
var isBun;
|
|
76
|
-
var init_compat = __esm(() => {
|
|
77
|
-
isBun = typeof globalThis.Bun !== "undefined";
|
|
78
|
-
});
|
|
65
|
+
var init_compat = () => {};
|
|
79
66
|
|
|
80
67
|
// src/utils/zip-extractor.ts
|
|
81
68
|
var exports_zip_extractor = {};
|
|
@@ -306,11 +293,11 @@ var SUBAGENT_NAMES = [
|
|
|
306
293
|
"council",
|
|
307
294
|
"councillor"
|
|
308
295
|
];
|
|
309
|
-
var
|
|
310
|
-
var ALL_AGENT_NAMES = [ORCHESTRATOR_NAME, ...SUBAGENT_NAMES];
|
|
296
|
+
var ALL_AGENT_NAMES = ["orchestrator", ...SUBAGENT_NAMES];
|
|
311
297
|
var PROTECTED_AGENTS = new Set(["orchestrator", "councillor"]);
|
|
312
|
-
var DEFAULT_TIMEOUT_MS = 2 * 60 * 1000;
|
|
313
298
|
var MAX_POLL_TIME_MS = 5 * 60 * 1000;
|
|
299
|
+
var PHASE_REMINDER_TEXT = `!IMPORTANT! Scheduler workflow: plan lanes/dependencies → dispatch background specialists → track task IDs → wait for hook-driven completion → reconcile terminal results → verify. Do not poll running jobs, consume running-job output, or advance dependent work. !END!`;
|
|
300
|
+
var PHASE_REMINDER = `<internal_reminder>${PHASE_REMINDER_TEXT}</internal_reminder>`;
|
|
314
301
|
// src/config/council-schema.ts
|
|
315
302
|
import { z } from "zod";
|
|
316
303
|
var ModelIdSchema = z.string().regex(/^[^/\s]+\/[^\s]+$/, 'Expected provider/model format (e.g. "openai/gpt-5.4-mini")');
|
|
@@ -357,17 +344,11 @@ var CouncilConfigSchema = z.object({
|
|
|
357
344
|
default_preset: z.string().default("default"),
|
|
358
345
|
councillor_execution_mode: CouncillorExecutionModeSchema.describe('Execution mode for councillors. "serial" runs them one at a time (required for single-model systems). "parallel" runs them concurrently (default, faster for multi-model systems).'),
|
|
359
346
|
councillor_retries: z.number().int().min(0).max(5).default(3).describe("Number of retry attempts for councillors that return empty responses " + "(e.g. due to provider rate limiting). Default: 3 retries."),
|
|
360
|
-
master: z.unknown().optional().describe("DEPRECATED — ignored. Council agent synthesizes directly.")
|
|
361
|
-
master_timeout: z.unknown().optional().describe('DEPRECATED — ignored. Use "timeout" instead.'),
|
|
362
|
-
master_fallback: z.unknown().optional().describe("DEPRECATED — ignored. No separate master session.")
|
|
347
|
+
master: z.unknown().optional().describe("DEPRECATED — ignored. Council agent synthesizes directly.")
|
|
363
348
|
}).transform((data) => {
|
|
364
349
|
const deprecated = [];
|
|
365
350
|
if (data.master !== undefined)
|
|
366
351
|
deprecated.push("master");
|
|
367
|
-
if (data.master_timeout !== undefined)
|
|
368
|
-
deprecated.push("master_timeout");
|
|
369
|
-
if (data.master_fallback !== undefined)
|
|
370
|
-
deprecated.push("master_fallback");
|
|
371
352
|
const legacyMasterModel = typeof data.master === "object" && data.master !== null && "model" in data.master && typeof data.master.model === "string" ? data.master.model : undefined;
|
|
372
353
|
return {
|
|
373
354
|
presets: data.presets,
|
|
@@ -409,15 +390,6 @@ var ManualPlanSchema = z2.object({
|
|
|
409
390
|
librarian: ManualAgentPlanSchema,
|
|
410
391
|
fixer: ManualAgentPlanSchema
|
|
411
392
|
}).strict();
|
|
412
|
-
var AgentModelChainSchema = z2.array(z2.string()).min(1);
|
|
413
|
-
var FallbackChainsSchema = z2.object({
|
|
414
|
-
orchestrator: AgentModelChainSchema.optional(),
|
|
415
|
-
oracle: AgentModelChainSchema.optional(),
|
|
416
|
-
designer: AgentModelChainSchema.optional(),
|
|
417
|
-
explorer: AgentModelChainSchema.optional(),
|
|
418
|
-
librarian: AgentModelChainSchema.optional(),
|
|
419
|
-
fixer: AgentModelChainSchema.optional()
|
|
420
|
-
}).catchall(AgentModelChainSchema);
|
|
421
393
|
var AgentOverrideConfigSchema = z2.object({
|
|
422
394
|
model: z2.union([
|
|
423
395
|
z2.string(),
|
|
@@ -480,14 +452,32 @@ var FailoverConfigSchema = z2.object({
|
|
|
480
452
|
enabled: z2.boolean().default(true),
|
|
481
453
|
timeoutMs: z2.number().min(0).default(15000),
|
|
482
454
|
retryDelayMs: z2.number().min(0).default(500),
|
|
483
|
-
chains: FallbackChainsSchema.default({}),
|
|
484
455
|
retry_on_empty: z2.boolean().default(true).describe("When true (default), empty provider responses are treated as failures, " + "triggering fallback/retry. Set to false to treat them as successes.")
|
|
485
|
-
});
|
|
456
|
+
}).strict();
|
|
486
457
|
var CompanionConfigSchema = z2.object({
|
|
487
458
|
enabled: z2.boolean().optional(),
|
|
459
|
+
binaryPath: z2.string().min(1).optional().describe("Path to a custom companion binary to launch."),
|
|
488
460
|
position: z2.enum(["bottom-right", "bottom-left", "top-right", "top-left"]).optional(),
|
|
489
|
-
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.")
|
|
490
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);
|
|
491
481
|
function validateCustomOnlyPromptFields(overrides, ctx, pathPrefix) {
|
|
492
482
|
for (const [name, override] of Object.entries(overrides)) {
|
|
493
483
|
const isBuiltInOrAlias = ALL_AGENT_NAMES.includes(name) || AGENT_ALIASES[name] !== undefined;
|
|
@@ -513,10 +503,7 @@ function validateCustomOnlyPromptFields(overrides, ctx, pathPrefix) {
|
|
|
513
503
|
var PluginConfigSchema = z2.object({
|
|
514
504
|
preset: z2.string().optional(),
|
|
515
505
|
setDefaultAgent: z2.boolean().optional(),
|
|
516
|
-
scoringEngineVersion: z2.enum(["v1", "v2-shadow", "v2"]).optional(),
|
|
517
|
-
balanceProviderUsage: z2.boolean().optional(),
|
|
518
506
|
autoUpdate: z2.boolean().optional().describe("Disable automatic installation of plugin updates when false. Defaults to true."),
|
|
519
|
-
manualPlan: ManualPlanSchema.optional(),
|
|
520
507
|
presets: z2.record(z2.string(), PresetSchema).optional(),
|
|
521
508
|
agents: z2.record(z2.string(), AgentOverrideConfigSchema).optional(),
|
|
522
509
|
disabled_agents: z2.array(z2.string()).optional().describe("Agent names to disable completely. " + "Disabled agents are not instantiated and cannot be delegated to. " + "Orchestrator and council internal agents (councillor) cannot be disabled. " + "By default, 'observer' is disabled. Remove it from this list and configure a vision-capable model to enable."),
|
|
@@ -528,7 +515,8 @@ var PluginConfigSchema = z2.object({
|
|
|
528
515
|
backgroundJobs: BackgroundJobsConfigSchema.optional(),
|
|
529
516
|
fallback: FailoverConfigSchema.optional(),
|
|
530
517
|
council: CouncilConfigSchema.optional(),
|
|
531
|
-
companion: CompanionConfigSchema.optional()
|
|
518
|
+
companion: CompanionConfigSchema.optional(),
|
|
519
|
+
acpAgents: AcpAgentsConfigSchema.optional()
|
|
532
520
|
}).superRefine((value, ctx) => {
|
|
533
521
|
if (value.agents) {
|
|
534
522
|
validateCustomOnlyPromptFields(value.agents, ctx, ["agents"]);
|
|
@@ -587,11 +575,23 @@ var CUSTOM_SKILLS = [
|
|
|
587
575
|
allowedAgents: ["orchestrator"],
|
|
588
576
|
sourcePath: "src/skills/deepwork"
|
|
589
577
|
},
|
|
578
|
+
{
|
|
579
|
+
name: "reflect",
|
|
580
|
+
description: "Review repeated work and suggest reusable workflow improvements",
|
|
581
|
+
allowedAgents: ["orchestrator"],
|
|
582
|
+
sourcePath: "src/skills/reflect"
|
|
583
|
+
},
|
|
590
584
|
{
|
|
591
585
|
name: "oh-my-opencode-slim",
|
|
592
586
|
description: "Configure, customize, and safely improve oh-my-opencode-slim setups",
|
|
593
587
|
allowedAgents: ["orchestrator"],
|
|
594
588
|
sourcePath: "src/skills/oh-my-opencode-slim"
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
name: "worktrees",
|
|
592
|
+
description: "Manage Git worktrees as OMO safe isolated coding lanes for complex/risky/parallel work",
|
|
593
|
+
allowedAgents: ["orchestrator"],
|
|
594
|
+
sourcePath: "src/skills/worktrees"
|
|
595
595
|
}
|
|
596
596
|
];
|
|
597
597
|
function getCustomSkillsDir() {
|
|
@@ -737,7 +737,11 @@ function generateLiteConfig(installConfig) {
|
|
|
737
737
|
config.companion = {
|
|
738
738
|
enabled: true,
|
|
739
739
|
position: "bottom-right",
|
|
740
|
-
size: "medium"
|
|
740
|
+
size: "medium",
|
|
741
|
+
gifPack: "default",
|
|
742
|
+
loopStyle: "classic",
|
|
743
|
+
speed: 1,
|
|
744
|
+
debug: false
|
|
741
745
|
};
|
|
742
746
|
}
|
|
743
747
|
return config;
|
|
@@ -745,12 +749,7 @@ function generateLiteConfig(installConfig) {
|
|
|
745
749
|
|
|
746
750
|
// src/cli/config-io.ts
|
|
747
751
|
var PACKAGE_NAME = "oh-my-opencode-slim";
|
|
748
|
-
var DEFAULT_OPENCODE_AGENTS_TO_DISABLE = [
|
|
749
|
-
"build",
|
|
750
|
-
"explore",
|
|
751
|
-
"general",
|
|
752
|
-
"plan"
|
|
753
|
-
];
|
|
752
|
+
var DEFAULT_OPENCODE_AGENTS_TO_DISABLE = ["explore", "general"];
|
|
754
753
|
function isString(value) {
|
|
755
754
|
return typeof value === "string";
|
|
756
755
|
}
|
|
@@ -1278,6 +1277,7 @@ function mergePluginConfigs(base, override) {
|
|
|
1278
1277
|
backgroundJobs: deepMerge(base.backgroundJobs, override.backgroundJobs),
|
|
1279
1278
|
fallback: deepMerge(base.fallback, override.fallback),
|
|
1280
1279
|
council: deepMerge(base.council, override.council),
|
|
1280
|
+
acpAgents: deepMerge(base.acpAgents, override.acpAgents),
|
|
1281
1281
|
companion: deepMerge(base.companion, override.companion)
|
|
1282
1282
|
};
|
|
1283
1283
|
}
|
|
@@ -1593,22 +1593,67 @@ function writeBackgroundSubagentsBlock(targetPath) {
|
|
|
1593
1593
|
writeFileSync2(targetPath, nextContent);
|
|
1594
1594
|
}
|
|
1595
1595
|
|
|
1596
|
-
// src/
|
|
1596
|
+
// src/companion/updater.ts
|
|
1597
|
+
init_compat();
|
|
1598
|
+
import { createHash } from "node:crypto";
|
|
1597
1599
|
import {
|
|
1598
1600
|
chmodSync,
|
|
1599
1601
|
copyFileSync as copyFileSync3,
|
|
1600
1602
|
existsSync as existsSync6,
|
|
1601
1603
|
mkdirSync as mkdirSync5,
|
|
1602
1604
|
mkdtempSync,
|
|
1605
|
+
readFileSync as readFileSync5,
|
|
1603
1606
|
renameSync as renameSync2,
|
|
1604
1607
|
rmSync as rmSync2,
|
|
1608
|
+
statSync as statSync4,
|
|
1605
1609
|
writeFileSync as writeFileSync3
|
|
1606
1610
|
} from "node:fs";
|
|
1607
|
-
import { homedir as homedir4, tmpdir } from "node:os";
|
|
1611
|
+
import { homedir as homedir4, platform, tmpdir } from "node:os";
|
|
1608
1612
|
import * as path2 from "node:path";
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
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
|
+
};
|
|
1612
1657
|
function getCompanionTarget() {
|
|
1613
1658
|
const p = process.platform;
|
|
1614
1659
|
const a = process.arch;
|
|
@@ -1631,47 +1676,109 @@ function getCompanionTarget() {
|
|
|
1631
1676
|
function getCompanionBinaryPath() {
|
|
1632
1677
|
const xdg = process.env.XDG_DATA_HOME?.trim();
|
|
1633
1678
|
const base = xdg && path2.isAbsolute(xdg) ? xdg : path2.join(homedir4(), ".local", "share");
|
|
1634
|
-
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");
|
|
1635
1680
|
}
|
|
1636
|
-
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
|
+
}
|
|
1637
1691
|
const target = getCompanionTarget();
|
|
1638
|
-
const finalBinaryPath = getCompanionBinaryPath();
|
|
1639
1692
|
if (!target) {
|
|
1640
1693
|
return {
|
|
1641
|
-
|
|
1642
|
-
|
|
1694
|
+
status: "failed",
|
|
1695
|
+
binaryPath,
|
|
1643
1696
|
error: `Unsupported platform/architecture: ${process.platform} ${process.arch}`
|
|
1644
1697
|
};
|
|
1645
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) {
|
|
1646
1743
|
const isWindows = process.platform === "win32";
|
|
1647
|
-
const
|
|
1648
|
-
const
|
|
1649
|
-
const
|
|
1650
|
-
if (
|
|
1651
|
-
console.log(` [dry-run] Detected companion target: ${target}`);
|
|
1652
|
-
console.log(` [dry-run] Would download archive: ${downloadUrl}`);
|
|
1653
|
-
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) {
|
|
1654
1748
|
return {
|
|
1655
|
-
|
|
1656
|
-
|
|
1749
|
+
status: "failed",
|
|
1750
|
+
binaryPath: finalBinaryPath,
|
|
1751
|
+
error: `Missing SHA256 checksum for companion archive: ${archiveName}`
|
|
1657
1752
|
};
|
|
1658
1753
|
}
|
|
1659
1754
|
let buffer;
|
|
1755
|
+
const controller = new AbortController;
|
|
1756
|
+
const timeout = setTimeout(() => controller.abort(), downloadTimeoutMs);
|
|
1660
1757
|
try {
|
|
1661
|
-
const res = await fetch(downloadUrl);
|
|
1758
|
+
const res = await fetch(downloadUrl, { signal: controller.signal });
|
|
1662
1759
|
if (!res.ok) {
|
|
1663
1760
|
return {
|
|
1664
|
-
|
|
1665
|
-
|
|
1761
|
+
status: "failed",
|
|
1762
|
+
binaryPath: finalBinaryPath,
|
|
1666
1763
|
error: `Failed to download companion binary (HTTP ${res.status}): ${res.statusText}`
|
|
1667
1764
|
};
|
|
1668
1765
|
}
|
|
1669
1766
|
buffer = await res.arrayBuffer();
|
|
1670
1767
|
} catch (err) {
|
|
1671
1768
|
return {
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
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"
|
|
1675
1782
|
};
|
|
1676
1783
|
}
|
|
1677
1784
|
let tempDir = "";
|
|
@@ -1685,14 +1792,13 @@ async function installCompanion(config) {
|
|
|
1685
1792
|
const { extractZip: extractZip2 } = await Promise.resolve().then(() => (init_zip_extractor(), exports_zip_extractor));
|
|
1686
1793
|
await extractZip2(archivePath, extractedDir);
|
|
1687
1794
|
} else {
|
|
1688
|
-
const
|
|
1689
|
-
const proc = crossSpawn2(["tar", "-xzf", archivePath, "-C", extractedDir]);
|
|
1795
|
+
const proc = crossSpawn(["tar", "-xzf", archivePath, "-C", extractedDir]);
|
|
1690
1796
|
const exitCode = await proc.exited;
|
|
1691
1797
|
if (exitCode !== 0) {
|
|
1692
1798
|
const stderr = await proc.stderr();
|
|
1693
1799
|
return {
|
|
1694
|
-
|
|
1695
|
-
|
|
1800
|
+
status: "failed",
|
|
1801
|
+
binaryPath: finalBinaryPath,
|
|
1696
1802
|
error: `Archive extraction failed (tar exited with ${exitCode}): ${stderr}`
|
|
1697
1803
|
};
|
|
1698
1804
|
}
|
|
@@ -1701,8 +1807,8 @@ async function installCompanion(config) {
|
|
|
1701
1807
|
const extractedBinaryPath = path2.join(extractedDir, binaryName);
|
|
1702
1808
|
if (!existsSync6(extractedBinaryPath)) {
|
|
1703
1809
|
return {
|
|
1704
|
-
|
|
1705
|
-
|
|
1810
|
+
status: "failed",
|
|
1811
|
+
binaryPath: finalBinaryPath,
|
|
1706
1812
|
error: `Binary ${binaryName} not found in extracted archive`
|
|
1707
1813
|
};
|
|
1708
1814
|
}
|
|
@@ -1714,15 +1820,24 @@ async function installCompanion(config) {
|
|
|
1714
1820
|
chmodSync(tmpFinalPath, 493);
|
|
1715
1821
|
}
|
|
1716
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
|
+
});
|
|
1717
1831
|
return {
|
|
1718
|
-
|
|
1719
|
-
|
|
1832
|
+
status: "installed",
|
|
1833
|
+
binaryPath: finalBinaryPath,
|
|
1834
|
+
version: manifest.version
|
|
1720
1835
|
};
|
|
1721
1836
|
} catch (err) {
|
|
1722
1837
|
return {
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
error: `Failed to install companion: ${
|
|
1838
|
+
status: "failed",
|
|
1839
|
+
binaryPath: finalBinaryPath,
|
|
1840
|
+
error: `Failed to install companion: ${formatError(err)}`
|
|
1726
1841
|
};
|
|
1727
1842
|
} finally {
|
|
1728
1843
|
if (tempDir) {
|
|
@@ -1732,10 +1847,131 @@ async function installCompanion(config) {
|
|
|
1732
1847
|
}
|
|
1733
1848
|
}
|
|
1734
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
|
+
}
|
|
1735
1971
|
// src/cli/system.ts
|
|
1736
1972
|
init_compat();
|
|
1737
1973
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
1738
|
-
import { statSync as
|
|
1974
|
+
import { statSync as statSync5 } from "node:fs";
|
|
1739
1975
|
var cachedOpenCodePath = null;
|
|
1740
1976
|
function resolvePathCommand(command) {
|
|
1741
1977
|
try {
|
|
@@ -1808,7 +2044,7 @@ function resolveOpenCodePath() {
|
|
|
1808
2044
|
if (opencodePath === "opencode")
|
|
1809
2045
|
continue;
|
|
1810
2046
|
try {
|
|
1811
|
-
const stat =
|
|
2047
|
+
const stat = statSync5(opencodePath);
|
|
1812
2048
|
if (stat.isFile()) {
|
|
1813
2049
|
cachedOpenCodePath = opencodePath;
|
|
1814
2050
|
return opencodePath;
|
|
@@ -1877,8 +2113,8 @@ var SYMBOLS = {
|
|
|
1877
2113
|
warn: `${YELLOW}[!]${RESET}`,
|
|
1878
2114
|
star: `${YELLOW}★${RESET}`
|
|
1879
2115
|
};
|
|
1880
|
-
var
|
|
1881
|
-
var GITHUB_URL = `https://github.com/${
|
|
2116
|
+
var GITHUB_REPO = "alvinunreal/oh-my-opencode-slim";
|
|
2117
|
+
var GITHUB_URL = `https://github.com/${GITHUB_REPO}`;
|
|
1882
2118
|
function printHeader(isUpdate) {
|
|
1883
2119
|
console.log();
|
|
1884
2120
|
console.log(`${BOLD}oh-my-opencode-slim ${isUpdate ? "Update" : "Install"}${RESET}`);
|
|
@@ -1921,7 +2157,7 @@ async function askToStarRepo(config) {
|
|
|
1921
2157
|
return;
|
|
1922
2158
|
try {
|
|
1923
2159
|
const { execFileSync } = await import("node:child_process");
|
|
1924
|
-
execFileSync("gh", ["api", "--silent", "--method", "PUT", `/user/starred/${
|
|
2160
|
+
execFileSync("gh", ["api", "--silent", "--method", "PUT", `/user/starred/${GITHUB_REPO}`], { stdio: "ignore", timeout: 1e4 });
|
|
1925
2161
|
printSuccess("Thanks for starring! ★");
|
|
1926
2162
|
} catch {
|
|
1927
2163
|
printInfo(`Couldn't star automatically. You can star manually:
|
|
@@ -2001,8 +2237,9 @@ ${block}
|
|
|
2001
2237
|
return { enabledNow: false, configuredTarget: target };
|
|
2002
2238
|
}
|
|
2003
2239
|
async function shouldInstallCompanion(config) {
|
|
2004
|
-
if (config.companion === "yes")
|
|
2240
|
+
if (config.companion === "yes") {
|
|
2005
2241
|
return true;
|
|
2242
|
+
}
|
|
2006
2243
|
if (config.companion === "no")
|
|
2007
2244
|
return false;
|
|
2008
2245
|
if (config.dryRun) {
|
|
@@ -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>;
|
|
@@ -5,10 +5,6 @@ export declare const DEFAULT_AGENT_MCPS: Record<AgentName, string[]>;
|
|
|
5
5
|
* Parse a list with wildcard and exclusion syntax.
|
|
6
6
|
*/
|
|
7
7
|
export declare function parseList(items: string[], allAvailable: string[]): string[];
|
|
8
|
-
/**
|
|
9
|
-
* Get available MCP names from schema and config.
|
|
10
|
-
*/
|
|
11
|
-
export declare function getAvailableMcpNames(config?: PluginConfig): string[];
|
|
12
8
|
/**
|
|
13
9
|
* Get the MCP list for an agent (from config or defaults).
|
|
14
10
|
*/
|