okstra 0.79.0 → 0.81.0
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.kr.md +11 -9
- package/README.md +11 -9
- package/docs/kr/architecture.md +7 -7
- package/docs/kr/cli.md +16 -4
- package/docs/project-structure-overview.md +6 -4
- package/docs/superpowers/plans/2026-06-14-host-runtime-auto-execution.md +1775 -0
- package/docs/superpowers/specs/2026-06-14-host-runtime-auto-execution-design.md +404 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/SKILL.md +1 -0
- package/runtime/python/okstra_ctl/render.py +11 -0
- package/runtime/python/okstra_ctl/run.py +25 -0
- package/runtime/skills/okstra-brief/SKILL.md +2 -8
- package/runtime/skills/okstra-coding-preflight/SKILL.md +1 -0
- package/runtime/skills/okstra-context-loader/SKILL.md +1 -0
- package/runtime/skills/okstra-convergence/SKILL.md +1 -0
- package/runtime/skills/okstra-inspect/SKILL.md +2 -2
- package/runtime/skills/okstra-report-writer/SKILL.md +1 -0
- package/runtime/skills/okstra-run/SKILL.md +7 -6
- package/runtime/skills/okstra-schedule/SKILL.md +2 -2
- package/runtime/skills/okstra-setup/SKILL.md +2 -2
- package/runtime/skills/okstra-team-contract/SKILL.md +1 -0
- package/src/cli-registry.mjs +8 -1
- package/src/doctor.mjs +17 -3
- package/src/install.mjs +203 -94
- package/src/render-bundle.mjs +38 -1
- package/src/run.mjs +201 -0
- package/src/runtime-manifest.mjs +59 -16
- package/src/runtime-resolver.mjs +123 -0
- package/src/uninstall.mjs +69 -26
package/src/run.mjs
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { runEnsureInstalled } from "./install.mjs";
|
|
2
|
+
import { runPythonModule } from "./_python-helper.mjs";
|
|
3
|
+
import { resolvePaths } from "./paths.mjs";
|
|
4
|
+
import { resolveRuntime } from "./runtime-resolver.mjs";
|
|
5
|
+
|
|
6
|
+
const USAGE = `okstra run — host-aware okstra execution front door
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
okstra run --project-root <dir> --project-id <id> --task-group <tg> \\
|
|
10
|
+
--task-id <tid> --task-type <type> [--lead-runtime auto|claude-code|codex|external]
|
|
11
|
+
|
|
12
|
+
Claude Code native execution remains the /okstra-run skill front door. Bare
|
|
13
|
+
'okstra run' orchestrates Codex and external/tmux CLI execution.
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
function flagValue(args, flag) {
|
|
17
|
+
const equals = args.find((arg) => arg.startsWith(`${flag}=`));
|
|
18
|
+
if (equals) return equals.slice(flag.length + 1);
|
|
19
|
+
const index = args.indexOf(flag);
|
|
20
|
+
return index >= 0 ? args[index + 1] : "";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function requestedRuntimeFromArgs(args) {
|
|
24
|
+
return flagValue(args, "--lead-runtime") || "auto";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function withoutLeadRuntimeFlags(args) {
|
|
28
|
+
const out = [];
|
|
29
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
30
|
+
const arg = args[i];
|
|
31
|
+
if (arg === "--lead-runtime") {
|
|
32
|
+
i += 1;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (arg.startsWith("--lead-runtime=")) continue;
|
|
36
|
+
out.push(arg);
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function extractLabel(stdout, label) {
|
|
42
|
+
const prefix = `${label}:`;
|
|
43
|
+
const line = String(stdout || "").split("\n").find((item) => item.startsWith(prefix));
|
|
44
|
+
return line ? line.slice(prefix.length).trim() : "";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function projectRootFromArgs(args) {
|
|
48
|
+
return flagValue(args, "--project-root");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function withoutDispatchOnlyFlags(args) {
|
|
52
|
+
const out = [];
|
|
53
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
54
|
+
const arg = args[i];
|
|
55
|
+
if (arg === "--enable-codex-report-writer") continue;
|
|
56
|
+
if (arg === "--report-writer-codex-model") {
|
|
57
|
+
i += 1;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (arg.startsWith("--report-writer-codex-model=")) continue;
|
|
61
|
+
out.push(arg);
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function renderArgs(args, resolution) {
|
|
67
|
+
return [
|
|
68
|
+
"--lead-runtime", resolution.resolvedRuntime,
|
|
69
|
+
"--lead-runtime-request", resolution.requestedRuntime,
|
|
70
|
+
"--runtime-resolution-json", JSON.stringify(resolution),
|
|
71
|
+
...withoutDispatchOnlyFlags(withoutLeadRuntimeFlags(args)),
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function codexDispatchArgs(args, projectRoot, runManifestPath) {
|
|
76
|
+
const out = ["--project-root", projectRoot, "--run-manifest", runManifestPath];
|
|
77
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
78
|
+
const arg = args[i];
|
|
79
|
+
if (arg === "--enable-codex-report-writer") {
|
|
80
|
+
out.push(arg);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (arg === "--report-writer-codex-model") {
|
|
84
|
+
out.push(arg, args[i + 1] ?? "");
|
|
85
|
+
i += 1;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (arg.startsWith("--report-writer-codex-model=")) out.push(arg);
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function buildRunPlan({ args, paths, resolution, runManifestPath = "" }) {
|
|
94
|
+
void paths;
|
|
95
|
+
const projectRoot = projectRootFromArgs(args);
|
|
96
|
+
if (resolution.resolvedRuntime === "claude-code") {
|
|
97
|
+
return {
|
|
98
|
+
ok: false,
|
|
99
|
+
reason: "Bare okstra run cannot invoke Claude Code TeamCreate / Agent(...). Use /okstra-run in Claude Code or pass --lead-runtime external|codex.",
|
|
100
|
+
commands: [],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (resolution.resolvedRuntime === "external") {
|
|
105
|
+
return {
|
|
106
|
+
ok: true,
|
|
107
|
+
reason: resolution.reason,
|
|
108
|
+
commands: [
|
|
109
|
+
{ name: "render-bundle", args: renderArgs(args, resolution), capture: true },
|
|
110
|
+
{ name: "team dispatch", args: ["dispatch", "--project-root", projectRoot, "--run-manifest", runManifestPath], capture: false },
|
|
111
|
+
{ name: "team await", args: ["await", "--project-root", projectRoot, "--run-manifest", runManifestPath], capture: false },
|
|
112
|
+
],
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (resolution.resolvedRuntime === "codex") {
|
|
117
|
+
return {
|
|
118
|
+
ok: true,
|
|
119
|
+
reason: resolution.reason,
|
|
120
|
+
commands: [
|
|
121
|
+
{ name: "codex-run", args: renderArgs(args, resolution), capture: true },
|
|
122
|
+
{ name: "codex-dispatch", args: codexDispatchArgs(args, projectRoot, runManifestPath), capture: false },
|
|
123
|
+
],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return { ok: false, reason: `unsupported resolved runtime: ${resolution.resolvedRuntime}`, commands: [] };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function ensureRuntime(requestedRuntime) {
|
|
131
|
+
return await runEnsureInstalled(["--runtime", requestedRuntime, "-q"]);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function runRenderCommand(command, paths) {
|
|
135
|
+
const result = await runPythonModule({
|
|
136
|
+
module: "okstra_ctl.run",
|
|
137
|
+
args: ["--workspace-root", paths.workspace, "--render-only", ...command.args],
|
|
138
|
+
stdio: "capture",
|
|
139
|
+
});
|
|
140
|
+
process.stdout.write(result.stdout);
|
|
141
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function runTeam(args) {
|
|
146
|
+
const { run: runTeamCommand } = await import("./team.mjs");
|
|
147
|
+
return await runTeamCommand(args);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function runCodexDispatch(args) {
|
|
151
|
+
const { run: runCodexDispatchCommand } = await import("./codex-dispatch.mjs");
|
|
152
|
+
return await runCodexDispatchCommand(args);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function run(args) {
|
|
156
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
157
|
+
process.stdout.write(USAGE);
|
|
158
|
+
return 0;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const paths = await resolvePaths();
|
|
162
|
+
const requestedRuntime = requestedRuntimeFromArgs(args);
|
|
163
|
+
const resolution = resolveRuntime({
|
|
164
|
+
requestedRuntime,
|
|
165
|
+
command: "run",
|
|
166
|
+
env: process.env,
|
|
167
|
+
capabilities: { tmuxAvailable: Boolean(process.env.TMUX) },
|
|
168
|
+
});
|
|
169
|
+
if (!resolution.ok) {
|
|
170
|
+
process.stderr.write(`error: ${resolution.reason}\n`);
|
|
171
|
+
return 2;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const firstPlan = buildRunPlan({ args, paths, resolution });
|
|
175
|
+
if (!firstPlan.ok) {
|
|
176
|
+
process.stderr.write(`error: ${firstPlan.reason}\n`);
|
|
177
|
+
return 2;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const ensureCode = await ensureRuntime(requestedRuntime);
|
|
181
|
+
if (ensureCode !== 0) return ensureCode;
|
|
182
|
+
|
|
183
|
+
const render = firstPlan.commands[0];
|
|
184
|
+
const prepared = await runRenderCommand(render, paths);
|
|
185
|
+
if (prepared.code !== 0) return prepared.code ?? 1;
|
|
186
|
+
|
|
187
|
+
const runManifestPath = extractLabel(prepared.stdout, "okstra run manifest");
|
|
188
|
+
if (!runManifestPath) {
|
|
189
|
+
process.stderr.write("error: render output did not include 'okstra run manifest:'\n");
|
|
190
|
+
return 1;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const plan = buildRunPlan({ args, paths, resolution, runManifestPath });
|
|
194
|
+
for (const command of plan.commands.slice(1)) {
|
|
195
|
+
const code = command.name.startsWith("team ")
|
|
196
|
+
? await runTeam(command.args)
|
|
197
|
+
: await runCodexDispatch(command.args);
|
|
198
|
+
if (code !== 0) return code;
|
|
199
|
+
}
|
|
200
|
+
return 0;
|
|
201
|
+
}
|
package/src/runtime-manifest.mjs
CHANGED
|
@@ -1,29 +1,72 @@
|
|
|
1
1
|
export const RUNTIMES_MANIFEST_REL = "installed-runtimes.json";
|
|
2
|
+
export const RUNTIME_MANIFEST_SCHEMA_VERSION = 2;
|
|
3
|
+
export const EXPLICIT_RUNTIME_VALUES = ["claude-code", "codex", "external"];
|
|
2
4
|
|
|
3
|
-
export function runtimeListForSelection(runtime) {
|
|
4
|
-
if (runtime === "all") return [
|
|
5
|
+
export function runtimeListForSelection(runtime, resolution = null) {
|
|
6
|
+
if (runtime === "all") return [...EXPLICIT_RUNTIME_VALUES];
|
|
7
|
+
if (runtime === "auto") {
|
|
8
|
+
const resolved = resolution?.resolvedRuntime;
|
|
9
|
+
return resolved && resolved !== "all" ? [resolved] : [];
|
|
10
|
+
}
|
|
5
11
|
return [runtime];
|
|
6
12
|
}
|
|
7
13
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
function includesClaudeRuntime(runtime, resolution = null) {
|
|
15
|
+
if (runtime === "all") return true;
|
|
16
|
+
if (runtime === "claude-code") return true;
|
|
17
|
+
return runtime === "auto" && resolution?.resolvedRuntime === "claude-code";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function defaultResolution(runtime) {
|
|
21
|
+
if (runtime === "auto") return null;
|
|
12
22
|
return {
|
|
23
|
+
requestedRuntime: runtime,
|
|
24
|
+
resolvedRuntime: runtime,
|
|
25
|
+
host: "explicit",
|
|
26
|
+
confidence: "high",
|
|
27
|
+
reason: `Explicit runtime '${runtime}' requested.`,
|
|
28
|
+
fallbackFrom: null,
|
|
29
|
+
warnings: [],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function buildRuntimeManifest(
|
|
34
|
+
runtime,
|
|
35
|
+
installedAt = new Date().toISOString(),
|
|
36
|
+
resolution = null,
|
|
37
|
+
installedAssets = null,
|
|
38
|
+
) {
|
|
39
|
+
const runtimeResolution = resolution ?? defaultResolution(runtime);
|
|
40
|
+
const includesClaude = includesClaudeRuntime(runtime, runtimeResolution);
|
|
41
|
+
const defaultAssets = {
|
|
42
|
+
sharedRuntime: true,
|
|
43
|
+
claudeSkills: includesClaude,
|
|
44
|
+
claudeAgents: includesClaude,
|
|
45
|
+
binWrappers: true,
|
|
46
|
+
templates: true,
|
|
47
|
+
schemas: true,
|
|
48
|
+
validators: true,
|
|
49
|
+
};
|
|
50
|
+
return {
|
|
51
|
+
schemaVersion: RUNTIME_MANIFEST_SCHEMA_VERSION,
|
|
13
52
|
installedAt,
|
|
14
|
-
runtime,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
claudeSkills: includesClaude,
|
|
19
|
-
claudeAgents: includesClaude,
|
|
20
|
-
codexAdapter: includesCodex,
|
|
21
|
-
externalAdapter: includesExternal,
|
|
22
|
-
},
|
|
23
|
-
version: 1,
|
|
53
|
+
installRequest: runtime,
|
|
54
|
+
runtimeResolution,
|
|
55
|
+
installedRuntimes: runtimeListForSelection(runtime, runtimeResolution),
|
|
56
|
+
installedAssets: { ...defaultAssets, ...(installedAssets ?? {}) },
|
|
24
57
|
};
|
|
25
58
|
}
|
|
26
59
|
|
|
60
|
+
export function installedRuntimesFromManifest(data) {
|
|
61
|
+
if (Array.isArray(data?.installedRuntimes)) return data.installedRuntimes;
|
|
62
|
+
if (Array.isArray(data?.runtimes)) return data.runtimes;
|
|
63
|
+
if (typeof data?.runtime === "string" && data.runtime) return runtimeListForSelection(data.runtime);
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
|
|
27
67
|
export function runtimeManifestIncludesClaudeAssets(data) {
|
|
68
|
+
if (data?.schemaVersion === RUNTIME_MANIFEST_SCHEMA_VERSION) {
|
|
69
|
+
return data?.installedAssets?.claudeSkills === true || data?.installedAssets?.claudeAgents === true;
|
|
70
|
+
}
|
|
28
71
|
return data?.assets?.claudeSkills === true || data?.assets?.claudeAgents === true;
|
|
29
72
|
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
export const RUNTIME_AUTO = "auto";
|
|
2
|
+
export const EXPLICIT_RUNTIMES = ["claude-code", "codex", "external"];
|
|
3
|
+
export const VALID_RUNTIME_REQUESTS = [RUNTIME_AUTO, ...EXPLICIT_RUNTIMES, "all"];
|
|
4
|
+
|
|
5
|
+
const VALID_HOSTS = new Set(EXPLICIT_RUNTIMES);
|
|
6
|
+
const VALID_REQUESTS = new Set(VALID_RUNTIME_REQUESTS);
|
|
7
|
+
|
|
8
|
+
function clean(value) {
|
|
9
|
+
return String(value ?? "").trim();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function normalizeRuntimeRequest(value) {
|
|
13
|
+
const runtime = clean(value) || RUNTIME_AUTO;
|
|
14
|
+
if (!VALID_REQUESTS.has(runtime)) {
|
|
15
|
+
throw new Error(`unknown runtime '${runtime}' (expected: ${VALID_RUNTIME_REQUESTS.join(", ")})`);
|
|
16
|
+
}
|
|
17
|
+
return runtime;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeCapabilities(capabilities = {}) {
|
|
21
|
+
return {
|
|
22
|
+
claudeAssetsInstalled: capabilities.claudeAssetsInstalled === true,
|
|
23
|
+
codexCliAvailable: capabilities.codexCliAvailable === true,
|
|
24
|
+
tmuxAvailable: capabilities.tmuxAvailable === true,
|
|
25
|
+
claudeSkillHandoff: capabilities.claudeSkillHandoff === true,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function explicitResult(runtime) {
|
|
30
|
+
return {
|
|
31
|
+
ok: true,
|
|
32
|
+
requestedRuntime: runtime,
|
|
33
|
+
resolvedRuntime: runtime,
|
|
34
|
+
host: "explicit",
|
|
35
|
+
confidence: "high",
|
|
36
|
+
reason: `Explicit runtime '${runtime}' requested.`,
|
|
37
|
+
fallbackFrom: null,
|
|
38
|
+
warnings: [],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const RUNTIME_FLAG_COMMANDS = new Set(["install", "ensure-installed", "doctor"]);
|
|
43
|
+
|
|
44
|
+
function explicitRuntimeFlag(command) {
|
|
45
|
+
return RUNTIME_FLAG_COMMANDS.has(command) ? "--runtime" : "--lead-runtime";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function okResult({ requestedRuntime, resolvedRuntime, host, reason, warnings = [] }) {
|
|
49
|
+
return {
|
|
50
|
+
ok: true,
|
|
51
|
+
requestedRuntime,
|
|
52
|
+
resolvedRuntime,
|
|
53
|
+
host,
|
|
54
|
+
confidence: "high",
|
|
55
|
+
reason,
|
|
56
|
+
fallbackFrom: null,
|
|
57
|
+
warnings,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function failResult(requestedRuntime, reason, warnings = []) {
|
|
62
|
+
return {
|
|
63
|
+
ok: false,
|
|
64
|
+
requestedRuntime,
|
|
65
|
+
resolvedRuntime: null,
|
|
66
|
+
host: "unknown",
|
|
67
|
+
confidence: "low",
|
|
68
|
+
reason,
|
|
69
|
+
fallbackFrom: null,
|
|
70
|
+
warnings,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function resolveRuntime({
|
|
75
|
+
requestedRuntime = RUNTIME_AUTO,
|
|
76
|
+
command = "run",
|
|
77
|
+
env = {},
|
|
78
|
+
capabilities = {},
|
|
79
|
+
} = {}) {
|
|
80
|
+
const request = normalizeRuntimeRequest(requestedRuntime);
|
|
81
|
+
const caps = normalizeCapabilities(capabilities);
|
|
82
|
+
|
|
83
|
+
if (request !== RUNTIME_AUTO) return explicitResult(request);
|
|
84
|
+
|
|
85
|
+
const rawHost = clean(env?.OKSTRA_RUNTIME_HOST);
|
|
86
|
+
if (rawHost) {
|
|
87
|
+
if (!VALID_HOSTS.has(rawHost)) {
|
|
88
|
+
return failResult(
|
|
89
|
+
request,
|
|
90
|
+
`invalid OKSTRA_RUNTIME_HOST '${rawHost}' (expected: ${EXPLICIT_RUNTIMES.join("|")}).`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
return okResult({
|
|
94
|
+
requestedRuntime: request,
|
|
95
|
+
resolvedRuntime: rawHost,
|
|
96
|
+
host: rawHost,
|
|
97
|
+
reason: `OKSTRA_RUNTIME_HOST=${rawHost} selected runtime '${rawHost}'.`,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (caps.claudeSkillHandoff) {
|
|
102
|
+
return okResult({
|
|
103
|
+
requestedRuntime: request,
|
|
104
|
+
resolvedRuntime: "claude-code",
|
|
105
|
+
host: "claude-code",
|
|
106
|
+
reason: "Claude Code skill handoff selected runtime 'claude-code'.",
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (caps.tmuxAvailable) {
|
|
111
|
+
return okResult({
|
|
112
|
+
requestedRuntime: request,
|
|
113
|
+
resolvedRuntime: "external",
|
|
114
|
+
host: "generic-terminal",
|
|
115
|
+
reason: "No Claude Code or Codex host signal; tmux is available.",
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return failResult(
|
|
120
|
+
request,
|
|
121
|
+
`host unknown: set OKSTRA_RUNTIME_HOST=claude-code|codex|external or pass ${explicitRuntimeFlag(command)} explicitly.`,
|
|
122
|
+
);
|
|
123
|
+
}
|
package/src/uninstall.mjs
CHANGED
|
@@ -51,22 +51,36 @@ const FALLBACK_AGENT_NAMES = [
|
|
|
51
51
|
"report-writer-worker.md",
|
|
52
52
|
];
|
|
53
53
|
|
|
54
|
-
const
|
|
55
|
-
const
|
|
54
|
+
const USER_HOME = homedir();
|
|
55
|
+
const CLAUDE_HOME = join(USER_HOME, ".claude");
|
|
56
|
+
const CODEX_HOME = join(USER_HOME, ".codex");
|
|
57
|
+
const OMP_AGENT_HOME = join(USER_HOME, ".omp", "agent");
|
|
58
|
+
const CLAUDE_SKILLS_DIR = join(CLAUDE_HOME, "skills");
|
|
59
|
+
const CODEX_SKILLS_DIR = join(CODEX_HOME, "skills");
|
|
60
|
+
const OMP_SKILLS_DIR = join(OMP_AGENT_HOME, "skills");
|
|
61
|
+
const CLAUDE_AGENTS_DIR = join(CLAUDE_HOME, "agents");
|
|
62
|
+
const FALLBACK_SKILL_ROOTS = [
|
|
63
|
+
CLAUDE_SKILLS_DIR,
|
|
64
|
+
CODEX_SKILLS_DIR,
|
|
65
|
+
OMP_SKILLS_DIR,
|
|
66
|
+
];
|
|
56
67
|
const SKILLS_MANIFEST_REL = "installed-skills.json";
|
|
57
68
|
const AGENTS_MANIFEST_REL = "installed-agents.json";
|
|
58
69
|
|
|
59
|
-
const USAGE = `okstra uninstall — remove installed runtime
|
|
70
|
+
const USAGE = `okstra uninstall — remove installed runtime and okstra skills
|
|
60
71
|
|
|
61
72
|
Usage:
|
|
62
73
|
okstra uninstall Remove ~/.okstra/{lib (python + validators),
|
|
63
74
|
bin/<known>, schemas, templates, version,
|
|
64
75
|
dev-link, installed-runtimes.json,
|
|
65
76
|
installed-skills.json, installed-agents.json} AND
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
77
|
+
every skill target recorded in
|
|
78
|
+
installed-skills.json
|
|
79
|
+
(~/.claude/skills, ~/.codex/skills, or
|
|
80
|
+
~/.omp/agent/skills). Claude worker agents are
|
|
81
|
+
removed from ~/.claude/agents when installed.
|
|
82
|
+
Missing manifests fall back to hard-coded
|
|
83
|
+
Claude okstra-* / *-worker.md names.
|
|
70
84
|
Preserves user data: recent.jsonl,
|
|
71
85
|
active.jsonl, projects/, archive/,
|
|
72
86
|
state.json, .locks/
|
|
@@ -74,7 +88,7 @@ Usage:
|
|
|
74
88
|
(<PROJECT>/.claude/settings.local.json symlink)
|
|
75
89
|
are NOT touched — remove them manually if needed.
|
|
76
90
|
okstra uninstall --purge Remove the entire ~/.okstra directory AND
|
|
77
|
-
|
|
91
|
+
okstra skills in Claude/Codex/OMP homes AND
|
|
78
92
|
~/.claude/agents/<*-worker.md> (DESTRUCTIVE).
|
|
79
93
|
Requires -y or an interactive confirmation
|
|
80
94
|
okstra uninstall --dry-run Print the plan without touching disk
|
|
@@ -138,7 +152,7 @@ export async function runUninstall(args) {
|
|
|
138
152
|
if (opts.purge) {
|
|
139
153
|
if (!opts.yes && !opts.dryRun) {
|
|
140
154
|
const ok = await promptConfirm(
|
|
141
|
-
`purge entire ${paths.home} AND
|
|
155
|
+
`purge entire ${paths.home} AND okstra skills in Claude/Codex/OMP homes? user data will be lost.`,
|
|
142
156
|
);
|
|
143
157
|
if (!ok) {
|
|
144
158
|
process.stdout.write("aborted.\n");
|
|
@@ -148,8 +162,10 @@ export async function runUninstall(args) {
|
|
|
148
162
|
if (!opts.quiet) process.stdout.write(`purging ${paths.home}\n`);
|
|
149
163
|
await removePath(paths.home, opts);
|
|
150
164
|
// Skills and worker agents live outside ~/.okstra — purge those too.
|
|
151
|
-
for (const
|
|
152
|
-
|
|
165
|
+
for (const root of FALLBACK_SKILL_ROOTS) {
|
|
166
|
+
for (const name of FALLBACK_SKILL_NAMES) {
|
|
167
|
+
await removePath(join(root, name), opts);
|
|
168
|
+
}
|
|
153
169
|
}
|
|
154
170
|
for (const name of FALLBACK_AGENT_NAMES) {
|
|
155
171
|
await removePath(join(CLAUDE_AGENTS_DIR, name), opts);
|
|
@@ -190,22 +206,26 @@ export async function runUninstall(args) {
|
|
|
190
206
|
// never removed it, leaving a stale final-report schema behind.
|
|
191
207
|
await removePath(join(paths.home, "schemas"), opts);
|
|
192
208
|
|
|
193
|
-
// Remove the skills we own. Manifest
|
|
194
|
-
//
|
|
195
|
-
//
|
|
209
|
+
// Remove the skills we own. Manifest v2 records every target root; legacy
|
|
210
|
+
// manifests fall back to Claude-only names when the runtime manifest says
|
|
211
|
+
// Claude assets may have been installed.
|
|
196
212
|
const runtimeManifest = await readRuntimeManifest(paths.home);
|
|
197
|
-
const
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
213
|
+
const skillTargets = await readSkillsManifest(paths.home, runtimeManifest);
|
|
214
|
+
for (const target of skillTargets) {
|
|
215
|
+
if (!opts.quiet) {
|
|
216
|
+
process.stdout.write(` skills: removing ${target.skills.length} entries from ${target.root}\n`);
|
|
217
|
+
}
|
|
218
|
+
for (const name of target.skills) {
|
|
219
|
+
await removePath(join(target.root, name), opts);
|
|
220
|
+
}
|
|
204
221
|
}
|
|
205
222
|
await removePath(join(paths.home, SKILLS_MANIFEST_REL), opts);
|
|
206
223
|
|
|
207
|
-
// Remove worker agents we own.
|
|
208
|
-
const
|
|
224
|
+
// Remove worker agents we own. Agents only live in Claude Code's agent home.
|
|
225
|
+
const shouldRemoveClaudeAgents =
|
|
226
|
+
skillTargets.some((target) => target.provider === "claude") ||
|
|
227
|
+
shouldUseClaudeAssetFallback(runtimeManifest);
|
|
228
|
+
const agentNames = shouldRemoveClaudeAgents ? await readAgentsManifest(paths.home) : [];
|
|
209
229
|
if (!opts.quiet) {
|
|
210
230
|
process.stdout.write(` agents: removing ${agentNames.length} entries from ${CLAUDE_AGENTS_DIR}\n`);
|
|
211
231
|
}
|
|
@@ -248,15 +268,38 @@ export function shouldUseClaudeAssetFallback(runtimeManifest) {
|
|
|
248
268
|
return runtimeManifestIncludesClaudeAssets(runtimeManifest);
|
|
249
269
|
}
|
|
250
270
|
|
|
251
|
-
|
|
271
|
+
function normalizeSkillTargets(data) {
|
|
272
|
+
if (Array.isArray(data?.targets)) {
|
|
273
|
+
return data.targets
|
|
274
|
+
.filter((target) => typeof target?.root === "string" && Array.isArray(target?.skills))
|
|
275
|
+
.map((target) => ({
|
|
276
|
+
provider: typeof target.provider === "string" ? target.provider : "unknown",
|
|
277
|
+
root: target.root,
|
|
278
|
+
skills: target.skills.filter((name) => typeof name === "string"),
|
|
279
|
+
}));
|
|
280
|
+
}
|
|
281
|
+
if (Array.isArray(data?.skills)) {
|
|
282
|
+
return [{
|
|
283
|
+
provider: "claude",
|
|
284
|
+
root: CLAUDE_SKILLS_DIR,
|
|
285
|
+
skills: data.skills.filter((name) => typeof name === "string"),
|
|
286
|
+
}];
|
|
287
|
+
}
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async function readSkillsManifest(home, runtimeManifest) {
|
|
252
292
|
try {
|
|
253
293
|
const raw = await fs.readFile(join(home, SKILLS_MANIFEST_REL), "utf8");
|
|
254
294
|
const data = JSON.parse(raw);
|
|
255
|
-
|
|
295
|
+
const targets = normalizeSkillTargets(data);
|
|
296
|
+
if (targets !== null) return targets;
|
|
256
297
|
} catch {
|
|
257
298
|
/* fall through */
|
|
258
299
|
}
|
|
259
|
-
return
|
|
300
|
+
return shouldUseClaudeAssetFallback(runtimeManifest)
|
|
301
|
+
? [{ provider: "claude", root: CLAUDE_SKILLS_DIR, skills: FALLBACK_SKILL_NAMES }]
|
|
302
|
+
: [];
|
|
260
303
|
}
|
|
261
304
|
|
|
262
305
|
async function readAgentsManifest(home) {
|