@otto-assistant/otto 0.1.0 → 0.1.1
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/dist/cli.js +406 -12
- package/dist/cli.js.map +1 -1
- package/dist/config.test.js +9 -9
- package/dist/config.test.js.map +1 -1
- package/dist/detect.test.js +4 -3
- package/dist/detect.test.js.map +1 -1
- package/dist/docker.d.ts +7 -0
- package/dist/docker.d.ts.map +1 -0
- package/dist/docker.js +17 -0
- package/dist/docker.js.map +1 -0
- package/dist/docker.test.d.ts +2 -0
- package/dist/docker.test.d.ts.map +1 -0
- package/dist/docker.test.js +12 -0
- package/dist/docker.test.js.map +1 -0
- package/dist/health.d.ts +4 -0
- package/dist/health.d.ts.map +1 -1
- package/dist/health.js +40 -1
- package/dist/health.js.map +1 -1
- package/dist/health.test.js +21 -2
- package/dist/health.test.js.map +1 -1
- package/dist/index.d.ts +11 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/installer.test.js +2 -2
- package/dist/installer.test.js.map +1 -1
- package/dist/lifecycle.d.ts +6 -0
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +26 -11
- package/dist/lifecycle.js.map +1 -1
- package/dist/lifecycle.test.js +5 -4
- package/dist/lifecycle.test.js.map +1 -1
- package/dist/manifest.js +4 -4
- package/dist/manifest.js.map +1 -1
- package/dist/skills-baseline.d.ts +7 -0
- package/dist/skills-baseline.d.ts.map +1 -0
- package/dist/skills-baseline.js +9 -0
- package/dist/skills-baseline.js.map +1 -0
- package/dist/skills.d.ts +110 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/skills.js +429 -0
- package/dist/skills.js.map +1 -0
- package/dist/skills.test.d.ts +2 -0
- package/dist/skills.test.d.ts.map +1 -0
- package/dist/skills.test.js +416 -0
- package/dist/skills.test.js.map +1 -0
- package/dist/tenant.d.ts +13 -0
- package/dist/tenant.d.ts.map +1 -0
- package/dist/tenant.js +105 -0
- package/dist/tenant.js.map +1 -0
- package/dist/tenant.test.d.ts +2 -0
- package/dist/tenant.test.d.ts.map +1 -0
- package/dist/tenant.test.js +37 -0
- package/dist/tenant.test.js.map +1 -0
- package/package.json +15 -5
- package/src/cli.ts +457 -12
- package/src/config.test.ts +9 -9
- package/src/detect.test.ts +4 -3
- package/src/docker.test.ts +12 -0
- package/src/docker.ts +23 -0
- package/src/health.test.ts +23 -1
- package/src/health.ts +45 -1
- package/src/index.ts +37 -3
- package/src/installer.test.ts +2 -2
- package/src/lifecycle.test.ts +6 -5
- package/src/lifecycle.ts +29 -10
- package/src/manifest.ts +4 -4
- package/src/skills-baseline.ts +14 -0
- package/src/skills.test.ts +503 -0
- package/src/skills.ts +512 -0
- package/src/tenant.test.ts +49 -0
- package/src/tenant.ts +120 -0
package/src/health.test.ts
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
+
import fs from "node:fs"
|
|
2
|
+
import os from "node:os"
|
|
3
|
+
import path from "node:path"
|
|
1
4
|
import { describe, expect, it } from "vitest"
|
|
2
5
|
import {
|
|
3
6
|
checkPackagePresence,
|
|
4
7
|
checkConfigHealth,
|
|
5
8
|
checkDirectoryHealth,
|
|
9
|
+
checkTenantHealth,
|
|
6
10
|
} from "./health.js"
|
|
7
11
|
|
|
8
12
|
describe("health", () => {
|
|
9
13
|
it("checkPackagePresence returns results for all manifest packages", { timeout: 30_000 }, () => {
|
|
10
14
|
const results = checkPackagePresence()
|
|
11
|
-
expect(results).toHaveLength(2) // opencode-ai + kimaki (not
|
|
15
|
+
expect(results).toHaveLength(2) // opencode-ai + kimaki (not mempalace — it's a plugin)
|
|
12
16
|
for (const r of results) {
|
|
13
17
|
expect(r).toHaveProperty("name")
|
|
14
18
|
expect(r).toHaveProperty("installed")
|
|
@@ -36,4 +40,22 @@ describe("health", () => {
|
|
|
36
40
|
expect(Array.isArray(results)).toBe(true)
|
|
37
41
|
expect(results.length).toBeGreaterThan(0)
|
|
38
42
|
})
|
|
43
|
+
|
|
44
|
+
it("checkTenantHealth reports missing memory bind root as error", () => {
|
|
45
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "otto-health-"))
|
|
46
|
+
const health = checkTenantHealth({ tenantPath: tmpDir })
|
|
47
|
+
const memoryItem = health.find((h) => h.name === "memory root")
|
|
48
|
+
expect(memoryItem).toBeDefined()
|
|
49
|
+
expect(memoryItem!.status).toBe("error")
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it("checkTenantHealth reports ok when all paths present", () => {
|
|
53
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "otto-health-"))
|
|
54
|
+
fs.mkdirSync(path.join(tmpDir, "memory"))
|
|
55
|
+
fs.mkdirSync(path.join(tmpDir, "projects"))
|
|
56
|
+
fs.writeFileSync(path.join(tmpDir, "compose.yml"), "services:", "utf-8")
|
|
57
|
+
const health = checkTenantHealth({ tenantPath: tmpDir })
|
|
58
|
+
const errors = health.filter((h) => h.status === "error")
|
|
59
|
+
expect(errors).toHaveLength(0)
|
|
60
|
+
})
|
|
39
61
|
})
|
package/src/health.ts
CHANGED
|
@@ -69,7 +69,7 @@ export function checkConfigHealth(): ConfigHealth {
|
|
|
69
69
|
|
|
70
70
|
const config = state.status === "ok" ? state.config : {}
|
|
71
71
|
const configuredPlugins = config.plugin ?? []
|
|
72
|
-
const memoryPluginEnabled = configuredPlugins.includes("
|
|
72
|
+
const memoryPluginEnabled = configuredPlugins.includes("mempalace")
|
|
73
73
|
|
|
74
74
|
const subagentPolicyInjected = subagentPolicyInjectedInPrompts(config)
|
|
75
75
|
|
|
@@ -112,3 +112,47 @@ export function checkDirectoryHealth(): HealthResult[] {
|
|
|
112
112
|
}
|
|
113
113
|
return results
|
|
114
114
|
}
|
|
115
|
+
|
|
116
|
+
export interface TenantHealthInput {
|
|
117
|
+
tenantPath: string
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function checkTenantHealth(input: TenantHealthInput): HealthResult[] {
|
|
121
|
+
const results: HealthResult[] = []
|
|
122
|
+
const { tenantPath } = input
|
|
123
|
+
|
|
124
|
+
if (fs.existsSync(tenantPath)) {
|
|
125
|
+
results.push({ name: "tenant path", status: "ok", message: `exists: ${tenantPath}` })
|
|
126
|
+
} else {
|
|
127
|
+
results.push({ name: "tenant path", status: "error", message: `missing: ${tenantPath}` })
|
|
128
|
+
return results
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const composePath = path.join(tenantPath, "compose.yml")
|
|
132
|
+
if (fs.existsSync(composePath)) {
|
|
133
|
+
results.push({ name: "compose.yml", status: "ok", message: "present" })
|
|
134
|
+
} else {
|
|
135
|
+
results.push({ name: "compose.yml", status: "error", message: "missing — run `otto tenant init`" })
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const memoryPath = path.join(tenantPath, "memory")
|
|
139
|
+
if (fs.existsSync(memoryPath)) {
|
|
140
|
+
try {
|
|
141
|
+
fs.accessSync(memoryPath, fs.constants.W_OK)
|
|
142
|
+
results.push({ name: "memory root", status: "ok", message: "writable" })
|
|
143
|
+
} catch {
|
|
144
|
+
results.push({ name: "memory root", status: "error", message: "not writable" })
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
results.push({ name: "memory root", status: "error", message: "missing — run `otto tenant init`" })
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const projectsPath = path.join(tenantPath, "projects")
|
|
151
|
+
if (fs.existsSync(projectsPath)) {
|
|
152
|
+
results.push({ name: "projects mount", status: "ok", message: "present" })
|
|
153
|
+
} else {
|
|
154
|
+
results.push({ name: "projects mount", status: "warn", message: "missing — will be created on first up" })
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return results
|
|
158
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -23,6 +23,40 @@ export {
|
|
|
23
23
|
export type { OpenCodeConfig, OttoConfig, OttoSubagentThreads, OttoConfigReadStatus, OpenCodeConfigReadStatus } from "./config.js"
|
|
24
24
|
export { installPackage, upgradePackage, installMissingPackages, planStableUpgrades } from "./installer.js"
|
|
25
25
|
export type { UpgradeMode } from "./installer.js"
|
|
26
|
-
export { hasKimakiBinary, isKimakiRunning, restartKimaki } from "./lifecycle.js"
|
|
27
|
-
export { checkPackagePresence, checkConfigHealth, checkDirectoryHealth } from "./health.js"
|
|
28
|
-
export type { HealthResult, PackageCheck, ConfigHealth } from "./health.js"
|
|
26
|
+
export { hasKimakiBinary, isKimakiRunning, restartKimaki, detectBinary } from "./lifecycle.js"
|
|
27
|
+
export { checkPackagePresence, checkConfigHealth, checkDirectoryHealth, checkTenantHealth } from "./health.js"
|
|
28
|
+
export type { HealthResult, PackageCheck, ConfigHealth, TenantHealthInput } from "./health.js"
|
|
29
|
+
export {
|
|
30
|
+
OPENCODE_SKILLS_DIR,
|
|
31
|
+
SKILLS_INDEX_PATH,
|
|
32
|
+
DEFAULT_SKILL_REPOS,
|
|
33
|
+
parseSkillMd,
|
|
34
|
+
loadSkillsIndex,
|
|
35
|
+
saveSkillsIndex,
|
|
36
|
+
isIndexStale,
|
|
37
|
+
ensureSkillsIndex,
|
|
38
|
+
searchSkills,
|
|
39
|
+
getAllIndexedSkills,
|
|
40
|
+
fetchRepoSkillsIndex,
|
|
41
|
+
ghApi,
|
|
42
|
+
fetchRepoDir,
|
|
43
|
+
fetchRepoFile,
|
|
44
|
+
listInstalledSkills,
|
|
45
|
+
installSkillFromIndex,
|
|
46
|
+
installSkillsBaseline,
|
|
47
|
+
removeSkill,
|
|
48
|
+
getConfiguredRepos,
|
|
49
|
+
} from "./skills.js"
|
|
50
|
+
export type { SkillMeta, SkillIndexEntry, SkillsIndex, RepoSyncResult } from "./skills.js"
|
|
51
|
+
export { GENTLEMAN_SKILLS_BASELINE } from "./skills-baseline.js"
|
|
52
|
+
export type { SkillsBootstrapReport } from "./skills-baseline.js"
|
|
53
|
+
export {
|
|
54
|
+
deriveComposeProjectName,
|
|
55
|
+
resolveTenantImage,
|
|
56
|
+
resolveTenantMode,
|
|
57
|
+
ensureTenantMemoryLayout,
|
|
58
|
+
ensureTenantScaffold,
|
|
59
|
+
} from "./tenant.js"
|
|
60
|
+
export type { TenantMode, TenantInitResult } from "./tenant.js"
|
|
61
|
+
export { buildComposeCommand, runCompose } from "./docker.js"
|
|
62
|
+
export type { DockerComposeCommand } from "./docker.js"
|
package/src/installer.test.ts
CHANGED
|
@@ -17,8 +17,8 @@ describe("installer", () => {
|
|
|
17
17
|
|
|
18
18
|
expect(result).toContain("opencode-ai")
|
|
19
19
|
expect(result).not.toContain("kimaki")
|
|
20
|
-
//
|
|
21
|
-
expect(result).not.toContain("
|
|
20
|
+
// mempalace is a plugin, not a global npm package
|
|
21
|
+
expect(result).not.toContain("mempalace")
|
|
22
22
|
})
|
|
23
23
|
|
|
24
24
|
it("planStableUpgrades returns empty when all packages match pinned", () => {
|
package/src/lifecycle.test.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest"
|
|
2
|
-
import { hasKimakiBinary, isKimakiRunning, restartKimaki } from "./lifecycle.js"
|
|
2
|
+
import { hasKimakiBinary, isKimakiRunning, restartKimaki, detectBinary } from "./lifecycle.js"
|
|
3
3
|
|
|
4
4
|
describe("lifecycle", () => {
|
|
5
|
+
it("detectBinary returns string or null", () => {
|
|
6
|
+
const result = detectBinary()
|
|
7
|
+
expect(result === null || typeof result === "string").toBe(true)
|
|
8
|
+
})
|
|
9
|
+
|
|
5
10
|
it("hasKimakiBinary returns boolean", () => {
|
|
6
11
|
const result = hasKimakiBinary()
|
|
7
12
|
expect(typeof result).toBe("boolean")
|
|
@@ -15,8 +20,4 @@ describe("lifecycle", () => {
|
|
|
15
20
|
it("restartKimaki is a function", () => {
|
|
16
21
|
expect(typeof restartKimaki).toBe("function")
|
|
17
22
|
})
|
|
18
|
-
|
|
19
|
-
it("restartKimaki throws descriptive error when kimaki not found", () => {
|
|
20
|
-
expect(typeof restartKimaki).toBe("function")
|
|
21
|
-
})
|
|
22
23
|
})
|
package/src/lifecycle.ts
CHANGED
|
@@ -1,17 +1,33 @@
|
|
|
1
1
|
import { execSync } from "node:child_process"
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
/** Ordered list of binary names to try — bridge is the otto fork, kimaki is upstream. */
|
|
4
|
+
const BIN_NAMES = ["bridge", "kimaki"] as const
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Detect the first available binary name from BIN_NAMES.
|
|
8
|
+
* Returns the binary name ("bridge" | "kimaki") or null if none found.
|
|
9
|
+
*/
|
|
10
|
+
export function detectBinary(): string | null {
|
|
11
|
+
for (const name of BIN_NAMES) {
|
|
12
|
+
try {
|
|
13
|
+
execSync(`which ${name}`, { encoding: "utf-8", stdio: "pipe" })
|
|
14
|
+
return name
|
|
15
|
+
} catch {
|
|
16
|
+
continue
|
|
17
|
+
}
|
|
9
18
|
}
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** @deprecated Use detectBinary() instead. Returns true if any known binary exists. */
|
|
23
|
+
export function hasKimakiBinary(): boolean {
|
|
24
|
+
return detectBinary() !== null
|
|
10
25
|
}
|
|
11
26
|
|
|
12
27
|
export function isKimakiRunning(): boolean {
|
|
28
|
+
// Check for both process names — "bridge" (otto fork) and "kimaki" (upstream)
|
|
13
29
|
try {
|
|
14
|
-
const output = execSync("pgrep -f kimaki", { encoding: "utf-8", stdio: "pipe" })
|
|
30
|
+
const output = execSync("pgrep -f 'kimaki|bridge'", { encoding: "utf-8", stdio: "pipe" })
|
|
15
31
|
return output.trim().length > 0
|
|
16
32
|
} catch {
|
|
17
33
|
return false
|
|
@@ -19,10 +35,13 @@ export function isKimakiRunning(): boolean {
|
|
|
19
35
|
}
|
|
20
36
|
|
|
21
37
|
export function restartKimaki(): void {
|
|
22
|
-
|
|
23
|
-
|
|
38
|
+
const bin = detectBinary()
|
|
39
|
+
if (!bin) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
"Neither 'bridge' nor 'kimaki' binary found. Install with: npm install -g @otto-assistant/bridge",
|
|
42
|
+
)
|
|
24
43
|
}
|
|
25
|
-
execSync(
|
|
44
|
+
execSync(`${bin} restart`, {
|
|
26
45
|
encoding: "utf-8",
|
|
27
46
|
stdio: "pipe",
|
|
28
47
|
timeout: 30_000,
|
package/src/manifest.ts
CHANGED
|
@@ -9,17 +9,17 @@ export interface Manifest {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export const MANIFEST: Manifest = {
|
|
12
|
-
version: "0.1.
|
|
12
|
+
version: "0.1.1",
|
|
13
13
|
packages: {
|
|
14
14
|
"opencode-ai": ">=1.0.115",
|
|
15
|
-
"@otto-assistant/bridge": ">=0.
|
|
15
|
+
"@otto-assistant/bridge": ">=0.6.0",
|
|
16
16
|
},
|
|
17
17
|
pinned: {
|
|
18
18
|
"opencode-ai": "1.2.20",
|
|
19
|
-
"@otto-assistant/bridge": "0.
|
|
19
|
+
"@otto-assistant/bridge": "0.6.2",
|
|
20
20
|
},
|
|
21
21
|
plugins: [
|
|
22
|
-
"
|
|
22
|
+
"mempalace",
|
|
23
23
|
],
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const GENTLEMAN_SKILLS_BASELINE: string[] = [
|
|
2
|
+
"critique",
|
|
3
|
+
"security-review",
|
|
4
|
+
"simplify",
|
|
5
|
+
"opensrc",
|
|
6
|
+
"playwriter",
|
|
7
|
+
"tuistory",
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
export interface SkillsBootstrapReport {
|
|
11
|
+
installed: string[]
|
|
12
|
+
alreadyPresent: string[]
|
|
13
|
+
failed: string[]
|
|
14
|
+
}
|