okstra 0.75.0 → 0.77.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 +5 -3
- package/README.md +5 -3
- package/bin/okstra +10 -2
- package/docs/kr/architecture.md +1 -1
- package/docs/kr/cli.md +20 -4
- package/docs/pr-template-usage.md +3 -3
- package/docs/superpowers/plans/2026-05-25-okstra-project-root-rename.md +1 -1
- package/docs/superpowers/specs/2026-06-12-codex-lead-adapter-design.md +358 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/codex-worker.md +1 -1
- package/runtime/agents/workers/gemini-worker.md +1 -1
- package/runtime/bin/lib/okstra/cli.sh +5 -1
- package/runtime/bin/lib/okstra/globals.sh +1 -0
- package/runtime/bin/lib/okstra/usage.sh +6 -4
- package/runtime/bin/okstra.sh +1 -0
- package/runtime/prompts/launch.template.md +4 -13
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +21 -0
- package/runtime/prompts/profiles/_implementation-executor.md +3 -6
- package/runtime/prompts/profiles/error-analysis.md +6 -0
- package/runtime/prompts/profiles/improvement-discovery.md +5 -0
- package/runtime/prompts/profiles/release-handoff.md +2 -2
- package/runtime/prompts/wizard/prompts.ko.json +12 -4
- package/runtime/python/okstra_ctl/analysis_packet.py +17 -0
- package/runtime/python/okstra_ctl/codex_dispatch.py +1484 -0
- package/runtime/python/okstra_ctl/lead_events.py +129 -0
- package/runtime/python/okstra_ctl/paths.py +3 -0
- package/runtime/python/okstra_ctl/pr_template.py +1 -1
- package/runtime/python/okstra_ctl/render.py +160 -29
- package/runtime/python/okstra_ctl/run.py +78 -15
- package/runtime/python/okstra_ctl/wizard.py +23 -9
- package/runtime/python/okstra_token_usage/codex.py +12 -7
- package/runtime/python/okstra_token_usage/collect.py +243 -54
- package/runtime/python/okstra_token_usage/gemini.py +12 -7
- package/runtime/skills/okstra-setup/SKILL.md +1 -1
- package/runtime/validators/validate-run.py +56 -22
- package/runtime/validators/validate_session_conformance.py +121 -4
- package/src/codex-dispatch.mjs +70 -0
- package/src/codex-run.mjs +68 -0
- package/src/doctor.mjs +40 -4
- package/src/install.mjs +122 -26
- package/src/paths.mjs +17 -3
- package/src/render-bundle.mjs +2 -0
- package/src/runtime-manifest.mjs +26 -0
- package/src/uninstall.mjs +26 -4
- /package/runtime/{skills/okstra-run/templates → templates/prd}/pr-body.template.md +0 -0
package/src/install.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { join, relative, resolve as resolveAbs } from "node:path";
|
|
|
5
5
|
import { getPackageRoot } from "./version.mjs";
|
|
6
6
|
import { resolvePaths } from "./paths.mjs";
|
|
7
7
|
import { fileExists } from "./_proc.mjs";
|
|
8
|
+
import { buildRuntimeManifest, RUNTIMES_MANIFEST_REL } from "./runtime-manifest.mjs";
|
|
8
9
|
|
|
9
10
|
const SKILLS_MANIFEST_REL = "installed-skills.json";
|
|
10
11
|
const AGENTS_MANIFEST_REL = "installed-agents.json";
|
|
@@ -36,6 +37,7 @@ const INSTALL_USAGE = `okstra install — install runtime into ~/.okstra
|
|
|
36
37
|
|
|
37
38
|
Usage:
|
|
38
39
|
okstra install Install/update runtime (copy mode)
|
|
40
|
+
okstra install --runtime <claude-code|codex|all>
|
|
39
41
|
okstra install --link <p> Dev only: symlink ~/.okstra/{lib/python,bin}
|
|
40
42
|
to the source repo at <p>. agents/ resolves
|
|
41
43
|
to <p>/agents. Not advertised to end users.
|
|
@@ -50,6 +52,7 @@ Effect (copy mode):
|
|
|
50
52
|
${"$HOME"}/.okstra/templates/settings.local.json <- runtime/templates/reports/settings.template.json
|
|
51
53
|
${"$HOME"}/.claude/skills/<name> <- runtime/skills/<name> (per skill)
|
|
52
54
|
${"$HOME"}/.claude/agents/<worker>.md <- runtime/agents/workers/<worker>.md
|
|
55
|
+
${"$HOME"}/.okstra/installed-runtimes.json <- manifest of installed runtime adapters
|
|
53
56
|
${"$HOME"}/.okstra/installed-skills.json <- manifest of installed skills
|
|
54
57
|
${"$HOME"}/.okstra/installed-agents.json <- manifest of installed workers
|
|
55
58
|
${"$HOME"}/.okstra/version <- installed package version stamp
|
|
@@ -78,11 +81,37 @@ const ENSURE_USAGE = `okstra ensure-installed — idempotent install check
|
|
|
78
81
|
|
|
79
82
|
Usage:
|
|
80
83
|
okstra ensure-installed Verify install; reinstall if version/files drift
|
|
84
|
+
okstra ensure-installed --runtime <claude-code|codex|all>
|
|
81
85
|
okstra ensure-installed -q Same, but suppress success output
|
|
82
86
|
|
|
83
87
|
Skills call this on every run. Returns 0 quickly when the install is fresh.
|
|
84
88
|
`;
|
|
85
89
|
|
|
90
|
+
const INSTALL_RUNTIMES = new Set(["claude-code", "codex", "all"]);
|
|
91
|
+
|
|
92
|
+
export function runtimeIncludesClaudeAssets(runtime) {
|
|
93
|
+
return runtime === "claude-code" || runtime === "all";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function readRuntimeFlag(args, index) {
|
|
97
|
+
const arg = args[index];
|
|
98
|
+
if (arg.startsWith("--runtime=")) {
|
|
99
|
+
return { value: arg.slice("--runtime=".length), nextIndex: index };
|
|
100
|
+
}
|
|
101
|
+
const next = args[index + 1];
|
|
102
|
+
if (!next || next.startsWith("--")) {
|
|
103
|
+
throw new Error("--runtime requires one of: claude-code, codex, all");
|
|
104
|
+
}
|
|
105
|
+
return { value: next, nextIndex: index + 1 };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function validateInstallRuntime(value) {
|
|
109
|
+
if (!INSTALL_RUNTIMES.has(value)) {
|
|
110
|
+
throw new Error(`unknown runtime '${value}' (expected: claude-code, codex, all)`);
|
|
111
|
+
}
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
|
|
86
115
|
async function hashFile(path) {
|
|
87
116
|
const buf = await fs.readFile(path);
|
|
88
117
|
return createHash("sha256").update(buf).digest("hex");
|
|
@@ -223,7 +252,7 @@ async function ensureSymlink(target, linkPath, opts) {
|
|
|
223
252
|
}
|
|
224
253
|
|
|
225
254
|
async function installLinkMode(repoPath, paths, opts) {
|
|
226
|
-
const { dryRun, quiet } = opts;
|
|
255
|
+
const { dryRun, quiet, runtime } = opts;
|
|
227
256
|
const repoAbs = resolveAbs(repoPath);
|
|
228
257
|
|
|
229
258
|
if (!(await dirExists(repoAbs))) {
|
|
@@ -240,6 +269,7 @@ async function installLinkMode(repoPath, paths, opts) {
|
|
|
240
269
|
|
|
241
270
|
if (!quiet) {
|
|
242
271
|
process.stdout.write(`installing okstra runtime in LINK mode (package ${paths.package})\n`);
|
|
272
|
+
process.stdout.write(` runtime: ${runtime}\n`);
|
|
243
273
|
process.stdout.write(` repo: ${repoAbs}\n`);
|
|
244
274
|
process.stdout.write(` home: ${paths.home}\n`);
|
|
245
275
|
}
|
|
@@ -271,14 +301,19 @@ async function installLinkMode(repoPath, paths, opts) {
|
|
|
271
301
|
if (!quiet) process.stdout.write(` bin/${name}: ${action}\n`);
|
|
272
302
|
}
|
|
273
303
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
304
|
+
if (runtimeIncludesClaudeAssets(runtime)) {
|
|
305
|
+
const skillResult = await installSkillsLink(repoAbs, { dryRun, quiet });
|
|
306
|
+
const leadSkillResult = await installLeadSkillLink(repoAbs, { dryRun, quiet });
|
|
307
|
+
await writeSkillsManifest(paths.home, [...skillResult.installed, ...leadSkillResult], { dryRun });
|
|
277
308
|
|
|
278
|
-
|
|
279
|
-
|
|
309
|
+
const agentResult = await installAgentsLink(repoAbs, { dryRun, quiet });
|
|
310
|
+
await writeAgentsManifest(paths.home, agentResult.installed, { dryRun });
|
|
311
|
+
} else if (!quiet) {
|
|
312
|
+
process.stdout.write(" claude assets: skipped for runtime=codex\n");
|
|
313
|
+
}
|
|
280
314
|
|
|
281
315
|
await installNamedTemplate(repoAbs, paths, { mode: "link", dryRun, quiet }, SETTINGS_TEMPLATE_DESCRIPTOR);
|
|
316
|
+
await writeRuntimeManifest(paths.home, runtime, { dryRun });
|
|
282
317
|
|
|
283
318
|
if (!dryRun) {
|
|
284
319
|
await writeFileAtomic(join(paths.home, "dev-link"), repoAbs + "\n", 0o644);
|
|
@@ -345,6 +380,20 @@ async function writeAgentsManifest(home, names, opts) {
|
|
|
345
380
|
);
|
|
346
381
|
}
|
|
347
382
|
|
|
383
|
+
async function writeRuntimeManifest(home, runtime, opts) {
|
|
384
|
+
const { dryRun = false } = opts ?? {};
|
|
385
|
+
const data = buildRuntimeManifest(runtime);
|
|
386
|
+
if (dryRun) {
|
|
387
|
+
process.stdout.write(`[dry-run] write runtime manifest: ${data.runtimes.join(",")}\n`);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
await writeFileAtomic(
|
|
391
|
+
join(home, RUNTIMES_MANIFEST_REL),
|
|
392
|
+
JSON.stringify(data, null, 2) + "\n",
|
|
393
|
+
0o644,
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
348
397
|
async function listWorkerFiles(workersRoot) {
|
|
349
398
|
try {
|
|
350
399
|
const entries = await fs.readdir(workersRoot, { withFileTypes: true });
|
|
@@ -553,19 +602,24 @@ async function installLeadSkillLink(repoAbs, opts) {
|
|
|
553
602
|
return [LEAD_SKILL_NAME];
|
|
554
603
|
}
|
|
555
604
|
|
|
556
|
-
function parseInstallArgs(args) {
|
|
605
|
+
export function parseInstallArgs(args) {
|
|
557
606
|
const result = {
|
|
558
607
|
dryRun: false,
|
|
559
608
|
refresh: false,
|
|
560
609
|
quiet: false,
|
|
561
610
|
linkRepo: null,
|
|
611
|
+
runtime: "claude-code",
|
|
562
612
|
};
|
|
563
613
|
for (let i = 0; i < args.length; i++) {
|
|
564
614
|
const a = args[i];
|
|
565
615
|
if (a === "--dry-run") result.dryRun = true;
|
|
566
616
|
else if (a === "--refresh") result.refresh = true;
|
|
567
617
|
else if (a === "-q" || a === "--quiet") result.quiet = true;
|
|
568
|
-
else if (a === "--
|
|
618
|
+
else if (a === "--runtime" || a.startsWith("--runtime=")) {
|
|
619
|
+
const parsed = readRuntimeFlag(args, i);
|
|
620
|
+
result.runtime = validateInstallRuntime(parsed.value);
|
|
621
|
+
i = parsed.nextIndex;
|
|
622
|
+
} else if (a === "--link") {
|
|
569
623
|
const next = args[i + 1];
|
|
570
624
|
if (!next || next.startsWith("--")) {
|
|
571
625
|
throw new Error("--link requires a repo path");
|
|
@@ -585,7 +639,13 @@ export async function runInstall(args) {
|
|
|
585
639
|
return 0;
|
|
586
640
|
}
|
|
587
641
|
|
|
588
|
-
|
|
642
|
+
let opts;
|
|
643
|
+
try {
|
|
644
|
+
opts = parseInstallArgs(args);
|
|
645
|
+
} catch (err) {
|
|
646
|
+
process.stderr.write(`error: ${err.message}\n`);
|
|
647
|
+
return 2;
|
|
648
|
+
}
|
|
589
649
|
const pkgRoot = getPackageRoot();
|
|
590
650
|
const runtimeRoot = join(pkgRoot, "runtime");
|
|
591
651
|
const paths = await resolvePaths();
|
|
@@ -607,6 +667,7 @@ export async function runInstall(args) {
|
|
|
607
667
|
|
|
608
668
|
if (!opts.quiet) {
|
|
609
669
|
process.stdout.write(`installing okstra runtime (package ${paths.package})\n`);
|
|
670
|
+
process.stdout.write(` runtime: ${opts.runtime}\n`);
|
|
610
671
|
process.stdout.write(` source: ${runtimeRoot}\n`);
|
|
611
672
|
process.stdout.write(` home: ${paths.home}\n`);
|
|
612
673
|
}
|
|
@@ -688,18 +749,23 @@ export async function runInstall(args) {
|
|
|
688
749
|
);
|
|
689
750
|
}
|
|
690
751
|
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
752
|
+
if (runtimeIncludesClaudeAssets(opts.runtime)) {
|
|
753
|
+
const skillResult = await installSkillsCopy(runtimeRoot, opts);
|
|
754
|
+
const leadSkillResult = await installLeadSkillCopy(runtimeRoot, opts);
|
|
755
|
+
await writeSkillsManifest(
|
|
756
|
+
paths.home,
|
|
757
|
+
[...skillResult.installed, ...leadSkillResult],
|
|
758
|
+
{ dryRun: opts.dryRun },
|
|
759
|
+
);
|
|
698
760
|
|
|
699
|
-
|
|
700
|
-
|
|
761
|
+
const agentResult = await installAgentsCopy(runtimeRoot, opts);
|
|
762
|
+
await writeAgentsManifest(paths.home, agentResult.installed, { dryRun: opts.dryRun });
|
|
763
|
+
} else if (!opts.quiet) {
|
|
764
|
+
process.stdout.write(" claude assets: skipped for runtime=codex\n");
|
|
765
|
+
}
|
|
701
766
|
|
|
702
767
|
await installNamedTemplate(runtimeRoot, paths, { mode: "copy", ...opts }, SETTINGS_TEMPLATE_DESCRIPTOR);
|
|
768
|
+
await writeRuntimeManifest(paths.home, opts.runtime, { dryRun: opts.dryRun });
|
|
703
769
|
|
|
704
770
|
if (!opts.dryRun) {
|
|
705
771
|
await writeFileAtomic(join(paths.home, "version"), paths.package + "\n", 0o644);
|
|
@@ -751,12 +817,38 @@ function summarise(label, result, target) {
|
|
|
751
817
|
);
|
|
752
818
|
}
|
|
753
819
|
|
|
820
|
+
export function parseEnsureInstalledArgs(args) {
|
|
821
|
+
const result = {
|
|
822
|
+
quiet: false,
|
|
823
|
+
runtime: "claude-code",
|
|
824
|
+
};
|
|
825
|
+
for (let i = 0; i < args.length; i++) {
|
|
826
|
+
const arg = args[i];
|
|
827
|
+
if (arg === "-q" || arg === "--quiet") {
|
|
828
|
+
result.quiet = true;
|
|
829
|
+
} else if (arg === "--runtime" || arg.startsWith("--runtime=")) {
|
|
830
|
+
const parsed = readRuntimeFlag(args, i);
|
|
831
|
+
result.runtime = validateInstallRuntime(parsed.value);
|
|
832
|
+
i = parsed.nextIndex;
|
|
833
|
+
} else {
|
|
834
|
+
throw new Error(`unknown argument '${arg}'`);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
return result;
|
|
838
|
+
}
|
|
839
|
+
|
|
754
840
|
export async function runEnsureInstalled(args) {
|
|
755
841
|
if (args.includes("--help") || args.includes("-h")) {
|
|
756
842
|
process.stdout.write(ENSURE_USAGE);
|
|
757
843
|
return 0;
|
|
758
844
|
}
|
|
759
|
-
|
|
845
|
+
let opts;
|
|
846
|
+
try {
|
|
847
|
+
opts = parseEnsureInstalledArgs(args);
|
|
848
|
+
} catch (err) {
|
|
849
|
+
process.stderr.write(`error: ${err.message}\n`);
|
|
850
|
+
return 2;
|
|
851
|
+
}
|
|
760
852
|
const paths = await resolvePaths();
|
|
761
853
|
|
|
762
854
|
const reasons = [];
|
|
@@ -769,18 +861,22 @@ export async function runEnsureInstalled(args) {
|
|
|
769
861
|
if (!(await fileExists(join(paths.home, "schemas", "final-report-v1.0.schema.json")))) {
|
|
770
862
|
reasons.push(`missing ${join(paths.home, "schemas", "final-report-v1.0.schema.json")}`);
|
|
771
863
|
}
|
|
772
|
-
if (
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
864
|
+
if (runtimeIncludesClaudeAssets(opts.runtime)) {
|
|
865
|
+
if (!(await fileExists(join(CLAUDE_SKILLS_DIR, "okstra-setup", "SKILL.md")))) {
|
|
866
|
+
reasons.push(`missing ${CLAUDE_SKILLS_DIR}/okstra-setup/SKILL.md`);
|
|
867
|
+
}
|
|
868
|
+
for (const reason of await agentDriftReasons(paths)) {
|
|
869
|
+
reasons.push(reason);
|
|
870
|
+
}
|
|
777
871
|
}
|
|
778
872
|
|
|
779
873
|
if (reasons.length === 0) {
|
|
780
|
-
if (!quiet) process.stdout.write(`okstra runtime OK (package ${paths.package})\n`);
|
|
874
|
+
if (!opts.quiet) process.stdout.write(`okstra runtime OK (package ${paths.package})\n`);
|
|
781
875
|
return 0;
|
|
782
876
|
}
|
|
783
877
|
|
|
784
878
|
process.stderr.write(`okstra runtime needs install: ${reasons.join("; ")}\n`);
|
|
785
|
-
|
|
879
|
+
const installArgs = ["--runtime", opts.runtime];
|
|
880
|
+
if (opts.quiet) installArgs.push("-q");
|
|
881
|
+
return await runInstall(installArgs);
|
|
786
882
|
}
|
package/src/paths.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
3
4
|
import { getPackageRoot, getPackageVersion } from "./version.mjs";
|
|
4
5
|
|
|
5
6
|
const USAGE = `okstra paths — print runtime paths
|
|
@@ -52,7 +53,7 @@ const SHELL_EXPORTS = [
|
|
|
52
53
|
];
|
|
53
54
|
|
|
54
55
|
export async function resolvePaths() {
|
|
55
|
-
const home = join(homedir(), ".okstra");
|
|
56
|
+
const home = process.env.OKSTRA_HOME?.trim() || join(homedir(), ".okstra");
|
|
56
57
|
const pkgRoot = getPackageRoot();
|
|
57
58
|
const devLink = await readSimpleFile(join(home, "dev-link"));
|
|
58
59
|
const runtimeRoot = devLink || join(pkgRoot, "runtime");
|
|
@@ -68,18 +69,31 @@ export async function resolvePaths() {
|
|
|
68
69
|
};
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
function workspacePythonpath(paths) {
|
|
73
|
+
const candidates = [
|
|
74
|
+
join(paths.workspace, "python"),
|
|
75
|
+
join(paths.workspace, "scripts"),
|
|
76
|
+
];
|
|
77
|
+
return candidates.find((candidate) => existsSync(candidate)) || "";
|
|
78
|
+
}
|
|
79
|
+
|
|
71
80
|
// Compose the PYTHONPATH spawned python3 subprocesses should see.
|
|
72
81
|
//
|
|
73
82
|
// Precedence (first wins):
|
|
74
83
|
// 1. process.env.PYTHONPATH (user override — e.g. in-repo `PYTHONPATH=scripts`
|
|
75
84
|
// for dev work without re-running `okstra install`).
|
|
76
|
-
// 2. The
|
|
85
|
+
// 2. The package/dev-link workspace runtime selected by `okstra paths`.
|
|
86
|
+
// 3. The installed user-runtime path (~/.okstra/lib/python).
|
|
77
87
|
//
|
|
78
88
|
// This is a colon-joined string suitable for direct assignment into the child
|
|
79
89
|
// process env. Empty segments are dropped so a missing env var does not produce
|
|
80
90
|
// a leading ':' (which Python treats as 'current directory').
|
|
81
91
|
export function buildPythonpath(paths) {
|
|
82
|
-
const fragments = [
|
|
92
|
+
const fragments = [
|
|
93
|
+
process.env.PYTHONPATH,
|
|
94
|
+
workspacePythonpath(paths),
|
|
95
|
+
paths.pythonpath,
|
|
96
|
+
].filter(
|
|
83
97
|
(s) => typeof s === "string" && s.length > 0,
|
|
84
98
|
);
|
|
85
99
|
return fragments.join(":");
|
package/src/render-bundle.mjs
CHANGED
|
@@ -16,6 +16,7 @@ Usage:
|
|
|
16
16
|
[--approved-plan <path>] [--workers <list>] [--directive <text>] \\
|
|
17
17
|
[--lead-model <m>] [--claude-model <m>] [--codex-model <m>] \\
|
|
18
18
|
[--gemini-model <m>] [--report-writer-model <m>] \\
|
|
19
|
+
[--lead-runtime claude-code|codex] \\
|
|
19
20
|
[--related-tasks <list>] [--base-ref <ref>] \\
|
|
20
21
|
[--clarification-response <path>] [--work-category <cat>] \\
|
|
21
22
|
[--stage <auto|N>] [--stages <csv>] [--pr-template-path <path>] \\
|
|
@@ -23,6 +24,7 @@ Usage:
|
|
|
23
24
|
|
|
24
25
|
--stage implementation / final-verification only
|
|
25
26
|
--stages release-handoff only: PR stage bundle csv (empty = whole-task)
|
|
27
|
+
--lead-runtime lead adapter marker; codex is render-only in this version
|
|
26
28
|
--pr-template-path release-handoff only
|
|
27
29
|
--fix-cycle entry phase only: record this re-entry of a done task as a
|
|
28
30
|
bug-fix cycle (yes opens/continues the cycle)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const RUNTIMES_MANIFEST_REL = "installed-runtimes.json";
|
|
2
|
+
|
|
3
|
+
export function runtimeListForSelection(runtime) {
|
|
4
|
+
return runtime === "all" ? ["claude-code", "codex"] : [runtime];
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function buildRuntimeManifest(runtime, installedAt = new Date().toISOString()) {
|
|
8
|
+
const includesClaude = runtime === "claude-code" || runtime === "all";
|
|
9
|
+
const includesCodex = runtime === "codex" || runtime === "all";
|
|
10
|
+
return {
|
|
11
|
+
version: 1,
|
|
12
|
+
installedAt,
|
|
13
|
+
runtime,
|
|
14
|
+
runtimes: runtimeListForSelection(runtime),
|
|
15
|
+
assets: {
|
|
16
|
+
sharedRuntime: true,
|
|
17
|
+
claudeSkills: includesClaude,
|
|
18
|
+
claudeAgents: includesClaude,
|
|
19
|
+
codexAdapter: includesCodex,
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function runtimeManifestIncludesClaudeAssets(data) {
|
|
25
|
+
return data?.assets?.claudeSkills === true || data?.assets?.claudeAgents === true;
|
|
26
|
+
}
|
package/src/uninstall.mjs
CHANGED
|
@@ -2,6 +2,10 @@ import { promises as fs } from "node:fs";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { resolvePaths } from "./paths.mjs";
|
|
5
|
+
import {
|
|
6
|
+
RUNTIMES_MANIFEST_REL,
|
|
7
|
+
runtimeManifestIncludesClaudeAssets,
|
|
8
|
+
} from "./runtime-manifest.mjs";
|
|
5
9
|
|
|
6
10
|
const BIN_ENTRYPOINTS = [
|
|
7
11
|
"okstra.sh",
|
|
@@ -56,8 +60,8 @@ const USAGE = `okstra uninstall — remove installed runtime from ~/.okstra, ~/.
|
|
|
56
60
|
Usage:
|
|
57
61
|
okstra uninstall Remove ~/.okstra/{lib (python + validators),
|
|
58
62
|
bin/<known>, schemas, templates, version,
|
|
59
|
-
dev-link, installed-
|
|
60
|
-
installed-agents.json} AND
|
|
63
|
+
dev-link, installed-runtimes.json,
|
|
64
|
+
installed-skills.json, installed-agents.json} AND
|
|
61
65
|
~/.claude/skills/<name> AND
|
|
62
66
|
~/.claude/agents/<worker>.md for every
|
|
63
67
|
entry in the install manifests (fallback:
|
|
@@ -188,7 +192,9 @@ export async function runUninstall(args) {
|
|
|
188
192
|
// Remove the skills we own. Manifest is authoritative; fall back to the
|
|
189
193
|
// hard-coded okstra-* names if it is missing (e.g. an install from an old
|
|
190
194
|
// version that did not write the manifest).
|
|
191
|
-
const
|
|
195
|
+
const runtimeManifest = await readRuntimeManifest(paths.home);
|
|
196
|
+
const shouldRemoveClaudeAssets = shouldUseClaudeAssetFallback(runtimeManifest);
|
|
197
|
+
const skillNames = shouldRemoveClaudeAssets ? await readSkillsManifest(paths.home) : [];
|
|
192
198
|
if (!opts.quiet) {
|
|
193
199
|
process.stdout.write(` skills: removing ${skillNames.length} entries from ${CLAUDE_SKILLS_DIR}\n`);
|
|
194
200
|
}
|
|
@@ -198,7 +204,7 @@ export async function runUninstall(args) {
|
|
|
198
204
|
await removePath(join(paths.home, SKILLS_MANIFEST_REL), opts);
|
|
199
205
|
|
|
200
206
|
// Remove worker agents we own. Same authoritative-manifest pattern as skills.
|
|
201
|
-
const agentNames = await readAgentsManifest(paths.home);
|
|
207
|
+
const agentNames = shouldRemoveClaudeAssets ? await readAgentsManifest(paths.home) : [];
|
|
202
208
|
if (!opts.quiet) {
|
|
203
209
|
process.stdout.write(` agents: removing ${agentNames.length} entries from ${CLAUDE_AGENTS_DIR}\n`);
|
|
204
210
|
}
|
|
@@ -218,6 +224,7 @@ export async function runUninstall(args) {
|
|
|
218
224
|
|
|
219
225
|
await removePath(join(paths.home, "version"), opts);
|
|
220
226
|
await removePath(join(paths.home, "dev-link"), opts);
|
|
227
|
+
await removePath(join(paths.home, RUNTIMES_MANIFEST_REL), opts);
|
|
221
228
|
|
|
222
229
|
if (!opts.quiet) {
|
|
223
230
|
process.stdout.write("done. user data preserved (recent.jsonl, projects/, archive/, ...).\n");
|
|
@@ -225,6 +232,21 @@ export async function runUninstall(args) {
|
|
|
225
232
|
return 0;
|
|
226
233
|
}
|
|
227
234
|
|
|
235
|
+
async function readRuntimeManifest(home) {
|
|
236
|
+
try {
|
|
237
|
+
const raw = await fs.readFile(join(home, RUNTIMES_MANIFEST_REL), "utf8");
|
|
238
|
+
const data = JSON.parse(raw);
|
|
239
|
+
return data && typeof data === "object" ? data : null;
|
|
240
|
+
} catch {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function shouldUseClaudeAssetFallback(runtimeManifest) {
|
|
246
|
+
if (runtimeManifest === null) return true;
|
|
247
|
+
return runtimeManifestIncludesClaudeAssets(runtimeManifest);
|
|
248
|
+
}
|
|
249
|
+
|
|
228
250
|
async function readSkillsManifest(home) {
|
|
229
251
|
try {
|
|
230
252
|
const raw = await fs.readFile(join(home, SKILLS_MANIFEST_REL), "utf8");
|
|
File without changes
|