dojocho 1.0.0 → 1.0.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/README.md +1 -1
- package/dist/index.js +127 -66
- package/package.json +8 -9
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ After install, the CLI is available as `dojo`.
|
|
|
17
17
|
## Usage
|
|
18
18
|
|
|
19
19
|
```sh
|
|
20
|
-
dojo setup
|
|
20
|
+
dojo setup # set up your dojo (auto-detects your agent via env vars)
|
|
21
21
|
dojo add effect-ts # install a training pack (a "dojo")
|
|
22
22
|
claude /kata # start practicing
|
|
23
23
|
```
|
package/dist/index.js
CHANGED
|
@@ -792,9 +792,9 @@ function markDone(root2) {
|
|
|
792
792
|
}
|
|
793
793
|
|
|
794
794
|
// src/commands/setup.ts
|
|
795
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync3, lstatSync, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
795
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, lstatSync, unlinkSync, writeFileSync as writeFileSync3, symlinkSync } from "fs";
|
|
796
796
|
import { execSync as execSync3 } from "child_process";
|
|
797
|
-
import { resolve as resolve5 } from "path";
|
|
797
|
+
import { resolve as resolve5, relative as relative4 } from "path";
|
|
798
798
|
|
|
799
799
|
// src/pm.ts
|
|
800
800
|
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
|
|
@@ -837,26 +837,26 @@ function pmCommands(root2) {
|
|
|
837
837
|
case "pnpm":
|
|
838
838
|
return {
|
|
839
839
|
name: pm,
|
|
840
|
-
installSilent: "pnpm install --ignore-workspace --
|
|
840
|
+
installSilent: "pnpm install --ignore-workspace --reporter=append-only",
|
|
841
841
|
add: (pkg) => `pnpm add ${pkg}`
|
|
842
842
|
};
|
|
843
843
|
case "yarn":
|
|
844
844
|
return {
|
|
845
845
|
name: pm,
|
|
846
|
-
installSilent: "yarn install
|
|
846
|
+
installSilent: "yarn install",
|
|
847
847
|
add: (pkg) => `yarn add ${pkg}`
|
|
848
848
|
};
|
|
849
849
|
case "bun":
|
|
850
850
|
return {
|
|
851
851
|
name: pm,
|
|
852
|
-
installSilent: "bun install
|
|
852
|
+
installSilent: "bun install",
|
|
853
853
|
add: (pkg) => `bun add ${pkg}`
|
|
854
854
|
};
|
|
855
855
|
case "npm":
|
|
856
856
|
default:
|
|
857
857
|
return {
|
|
858
858
|
name: pm,
|
|
859
|
-
installSilent: "npm install --
|
|
859
|
+
installSilent: "npm install --no-progress",
|
|
860
860
|
add: (pkg) => `npm install ${pkg}`
|
|
861
861
|
};
|
|
862
862
|
}
|
|
@@ -864,11 +864,18 @@ function pmCommands(root2) {
|
|
|
864
864
|
|
|
865
865
|
// src/commands/setup.ts
|
|
866
866
|
var AGENTS = {
|
|
867
|
-
claude: { dir: ".claude", hasSettings: true },
|
|
868
|
-
opencode: { dir: ".opencode", hasSettings: false },
|
|
869
|
-
codex: { dir: ".codex", hasSettings: false },
|
|
870
|
-
gemini: { dir: ".gemini", hasSettings: false }
|
|
867
|
+
claude: { dir: ".claude", commandsDir: "commands", hasSettings: true, envVars: ["CLAUDECODE"] },
|
|
868
|
+
opencode: { dir: ".opencode", commandsDir: "commands", hasSettings: false, envVars: ["OPENCODE"] },
|
|
869
|
+
codex: { dir: ".codex", commandsDir: "commands", hasSettings: false, envVars: ["CODEX_THREAD_ID"] },
|
|
870
|
+
gemini: { dir: ".gemini", commandsDir: "commands", hasSettings: false, envVars: ["GEMINI_CLI"] },
|
|
871
|
+
pi: { dir: ".pi", commandsDir: "prompts", hasSettings: false, envVars: ["PI_CODING_AGENT"] }
|
|
871
872
|
};
|
|
873
|
+
var AGENTS_COMMANDS_DIR = ".agents/commands";
|
|
874
|
+
function detectAgentsFromEnv() {
|
|
875
|
+
return Object.keys(AGENTS).filter(
|
|
876
|
+
(a) => AGENTS[a].envVars.some((v) => Boolean(process.env[v]))
|
|
877
|
+
);
|
|
878
|
+
}
|
|
872
879
|
var CLAUDE_SETTINGS = {
|
|
873
880
|
permissions: {
|
|
874
881
|
allow: [
|
|
@@ -886,28 +893,6 @@ var DEFAULT_DOJO_MD = `!\`dojo $ARGUMENTS\`
|
|
|
886
893
|
|
|
887
894
|
Follow any \`<dojo:prompt>\` instructions in the output.
|
|
888
895
|
`;
|
|
889
|
-
var DEFAULT_KATA_MD = `!\`dojo status\`
|
|
890
|
-
|
|
891
|
-
## Protocol
|
|
892
|
-
|
|
893
|
-
CLI output uses XML tags to separate directives from student content:
|
|
894
|
-
|
|
895
|
-
- \`<dojo:status>\` \u2014 Machine state. Parse the \`run:\` line and execute it.
|
|
896
|
-
- \`<dojo:prompt>\` \u2014 Interaction spec. Follow the instructions inside.
|
|
897
|
-
- \`<dojo:sensei>\` \u2014 Teaching material. Internalize but never show verbatim.
|
|
898
|
-
- \`<dojo:learnings>\` \u2014 Prior student observations. Use to personalize teaching.
|
|
899
|
-
- **Unwrapped text** \u2014 Student-facing. Display as-is.
|
|
900
|
-
|
|
901
|
-
## Flow
|
|
902
|
-
|
|
903
|
-
1. Parse \`<dojo:status>\` above.
|
|
904
|
-
2. If state is \`complete\`, congratulate the student.
|
|
905
|
-
3. If state is \`no-dojo\`, tell them to run \`dojo add <source>\`.
|
|
906
|
-
4. Otherwise, execute the \`run:\` command.
|
|
907
|
-
5. After running, follow any \`<dojo:prompt>\` instructions.
|
|
908
|
-
6. Use \`<dojo:sensei>\` content to guide teaching \u2014 never paste it to the student.
|
|
909
|
-
7. If \`<dojo:learnings>\` is present, use it to personalize teaching based on prior observations.
|
|
910
|
-
`;
|
|
911
896
|
var DEFAULT_KATA_MD_CLAUDE = `!\`dojo status\`
|
|
912
897
|
|
|
913
898
|
## Identity
|
|
@@ -973,14 +958,16 @@ function setup(root2, args2) {
|
|
|
973
958
|
const explicit = Object.keys(AGENTS).filter(
|
|
974
959
|
(a) => args2.includes(`--${a}`)
|
|
975
960
|
);
|
|
976
|
-
|
|
961
|
+
const detected = explicit.length > 0 ? explicit : detectAgentsFromEnv();
|
|
962
|
+
if (detected.length === 0) {
|
|
977
963
|
promptAgents();
|
|
978
964
|
return;
|
|
979
965
|
}
|
|
980
966
|
scaffold2(root2);
|
|
981
|
-
setupAgents(root2,
|
|
982
|
-
const kataCmd =
|
|
983
|
-
|
|
967
|
+
setupAgents(root2, detected);
|
|
968
|
+
const kataCmd = detected.length === 1 ? `${detected[0]} "/kata"` : "/kata in your agent prompt";
|
|
969
|
+
const suffix = explicit.length === 0 ? ` (detected from env: ${detected.join(", ")})` : "";
|
|
970
|
+
console.log(`Dojo ready${suffix}.
|
|
984
971
|
|
|
985
972
|
Add a dojo with: ${CLI} add <source>
|
|
986
973
|
Then use: ${kataCmd}`);
|
|
@@ -990,7 +977,8 @@ function promptAgents() {
|
|
|
990
977
|
`- "Claude Code" \u2192 --claude`,
|
|
991
978
|
`- "OpenCode" \u2192 --opencode`,
|
|
992
979
|
`- "Codex" \u2192 --codex`,
|
|
993
|
-
`- "Gemini CLI" \u2192 --gemini
|
|
980
|
+
`- "Gemini CLI" \u2192 --gemini`,
|
|
981
|
+
`- "Pi" \u2192 --pi`
|
|
994
982
|
].join("\n");
|
|
995
983
|
console.log(prompt(`${invokeAsk("multiSelect")} to ask the student:
|
|
996
984
|
Which coding agents do you use?
|
|
@@ -1041,32 +1029,46 @@ function scaffold2(root2) {
|
|
|
1041
1029
|
}
|
|
1042
1030
|
const pm = pmCommands(root2);
|
|
1043
1031
|
console.log("Installing @dojocho/config...");
|
|
1044
|
-
|
|
1032
|
+
try {
|
|
1033
|
+
execSync3(pm.add("@dojocho/config"), { cwd: root2, stdio: "pipe" });
|
|
1034
|
+
} catch (err) {
|
|
1035
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1036
|
+
const firstLine = msg.split("\n").find((l) => l.includes("ERR_") || l.includes("error")) ?? msg.split("\n")[0];
|
|
1037
|
+
console.warn(`! Could not install @dojocho/config: ${firstLine.trim()}`);
|
|
1038
|
+
console.warn(` Continuing setup. Install it manually later (e.g. when authoring katas):`);
|
|
1039
|
+
console.warn(` ${pm.add("@dojocho/config")}`);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
function symlinkCanonical(root2, agent, name) {
|
|
1043
|
+
const cfg = AGENTS[agent];
|
|
1044
|
+
const targetDir = resolve5(root2, cfg.dir, cfg.commandsDir);
|
|
1045
|
+
const target = resolve5(targetDir, `${name}.md`);
|
|
1046
|
+
const canonical = resolve5(root2, AGENTS_COMMANDS_DIR, `${name}.md`);
|
|
1047
|
+
try {
|
|
1048
|
+
lstatSync(target);
|
|
1049
|
+
unlinkSync(target);
|
|
1050
|
+
} catch {
|
|
1051
|
+
}
|
|
1052
|
+
symlinkSync(relative4(targetDir, canonical), target);
|
|
1053
|
+
}
|
|
1054
|
+
function writeCanonicalCommands(root2) {
|
|
1055
|
+
const canonicalDir = resolve5(root2, AGENTS_COMMANDS_DIR);
|
|
1056
|
+
mkdirSync3(canonicalDir, { recursive: true });
|
|
1057
|
+
const canonicalDojo = resolve5(canonicalDir, "dojo.md");
|
|
1058
|
+
if (!existsSync6(canonicalDojo)) writeFileSync3(canonicalDojo, DEFAULT_DOJO_MD);
|
|
1059
|
+
const canonicalKata = resolve5(canonicalDir, "kata.md");
|
|
1060
|
+
if (!existsSync6(canonicalKata)) writeFileSync3(canonicalKata, DEFAULT_KATA_MD_CLAUDE);
|
|
1045
1061
|
}
|
|
1046
1062
|
function setupAgents(root2, agents) {
|
|
1063
|
+
writeCanonicalCommands(root2);
|
|
1047
1064
|
for (const agent of agents) {
|
|
1048
|
-
const
|
|
1049
|
-
mkdirSync3(resolve5(root2, dir,
|
|
1050
|
-
mkdirSync3(resolve5(root2, dir, "skills"), { recursive: true });
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
}
|
|
1056
|
-
if (!existsSync6(dojoMd)) {
|
|
1057
|
-
writeFileSync3(dojoMd, DEFAULT_DOJO_MD);
|
|
1058
|
-
}
|
|
1059
|
-
const kataMdContent = agent === "claude" ? DEFAULT_KATA_MD_CLAUDE : DEFAULT_KATA_MD;
|
|
1060
|
-
const kataMd = resolve5(root2, dir, "commands", "kata.md");
|
|
1061
|
-
try {
|
|
1062
|
-
if (lstatSync(kataMd).isSymbolicLink()) unlinkSync(kataMd);
|
|
1063
|
-
} catch {
|
|
1064
|
-
}
|
|
1065
|
-
if (!existsSync6(kataMd)) {
|
|
1066
|
-
writeFileSync3(kataMd, kataMdContent);
|
|
1067
|
-
}
|
|
1068
|
-
if (AGENTS[agent].hasSettings) {
|
|
1069
|
-
const settingsPath = resolve5(root2, dir, "settings.json");
|
|
1065
|
+
const cfg = AGENTS[agent];
|
|
1066
|
+
mkdirSync3(resolve5(root2, cfg.dir, cfg.commandsDir), { recursive: true });
|
|
1067
|
+
mkdirSync3(resolve5(root2, cfg.dir, "skills"), { recursive: true });
|
|
1068
|
+
symlinkCanonical(root2, agent, "dojo");
|
|
1069
|
+
symlinkCanonical(root2, agent, "kata");
|
|
1070
|
+
if (cfg.hasSettings) {
|
|
1071
|
+
const settingsPath = resolve5(root2, cfg.dir, "settings.json");
|
|
1070
1072
|
writeFileSync3(settingsPath, JSON.stringify(CLAUDE_SETTINGS, null, 2) + "\n");
|
|
1071
1073
|
}
|
|
1072
1074
|
}
|
|
@@ -1078,9 +1080,9 @@ function configuredAgents(root2) {
|
|
|
1078
1080
|
}
|
|
1079
1081
|
|
|
1080
1082
|
// src/commands/add.ts
|
|
1081
|
-
import { existsSync as existsSync8, mkdirSync as mkdirSync4, cpSync, renameSync, unlinkSync as unlinkSync3, symlinkSync, readdirSync as readdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync4, rmSync as rmSync2 } from "fs";
|
|
1083
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, cpSync, renameSync, unlinkSync as unlinkSync3, symlinkSync as symlinkSync2, readdirSync as readdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync4, rmSync as rmSync2 } from "fs";
|
|
1082
1084
|
import { execSync as execSync4, execFileSync } from "child_process";
|
|
1083
|
-
import { resolve as resolve7, relative as
|
|
1085
|
+
import { resolve as resolve7, relative as relative5 } from "path";
|
|
1084
1086
|
import { tmpdir } from "os";
|
|
1085
1087
|
|
|
1086
1088
|
// src/commands/remove.ts
|
|
@@ -1218,7 +1220,15 @@ function installExtracted(root2, extractedDir, source, force) {
|
|
|
1218
1220
|
const pkgPath = resolve7(extractedDir, "package.json");
|
|
1219
1221
|
if (existsSync8(pkgPath)) {
|
|
1220
1222
|
console.log(`Installing ${name} dependencies...`);
|
|
1221
|
-
|
|
1223
|
+
try {
|
|
1224
|
+
execSync4(pm.installSilent, { cwd: extractedDir, stdio: "pipe" });
|
|
1225
|
+
} catch (err) {
|
|
1226
|
+
const e = err;
|
|
1227
|
+
const detail = e.stderr?.toString().trim() || e.stdout?.toString().trim() || e.message || "unknown error";
|
|
1228
|
+
throw new Error(`Failed to install ${name} dependencies via ${pm.name}:
|
|
1229
|
+
|
|
1230
|
+
${detail}`);
|
|
1231
|
+
}
|
|
1222
1232
|
}
|
|
1223
1233
|
handleExisting(root2, name, force);
|
|
1224
1234
|
const targetPath = dojoDir(root2, name);
|
|
@@ -1318,9 +1328,9 @@ function finalize(root2, name, targetPath) {
|
|
|
1318
1328
|
}
|
|
1319
1329
|
}
|
|
1320
1330
|
const dojo = loadConfig(root2);
|
|
1321
|
-
const katasInclude = `${
|
|
1331
|
+
const katasInclude = `${relative5(root2, dojo.katasPath)}/**/*.ts`;
|
|
1322
1332
|
const tsconfigPath = resolve7(root2, "tsconfig.json");
|
|
1323
|
-
const extendsPath = `./${
|
|
1333
|
+
const extendsPath = `./${relative5(root2, resolve7(targetPath, "tsconfig.json"))}`;
|
|
1324
1334
|
writeFileSync4(
|
|
1325
1335
|
tsconfigPath,
|
|
1326
1336
|
JSON.stringify(
|
|
@@ -1350,7 +1360,7 @@ function symlinkDir(sourceDir, targetDir, filter) {
|
|
|
1350
1360
|
if (!filter(entry)) continue;
|
|
1351
1361
|
const link = resolve7(targetDir, entry.name);
|
|
1352
1362
|
if (existsSync8(link)) unlinkSync3(link);
|
|
1353
|
-
|
|
1363
|
+
symlinkSync2(relative5(targetDir, resolve7(sourceDir, entry.name)), link);
|
|
1354
1364
|
}
|
|
1355
1365
|
}
|
|
1356
1366
|
function runLifecycleScript(root2, dojoPath, script) {
|
|
@@ -1438,6 +1448,55 @@ function status2(root2, _args) {
|
|
|
1438
1448
|
}));
|
|
1439
1449
|
}
|
|
1440
1450
|
|
|
1451
|
+
// src/commands/ui.ts
|
|
1452
|
+
import { existsSync as existsSync9 } from "fs";
|
|
1453
|
+
import { spawn } from "child_process";
|
|
1454
|
+
import { dirname as dirname4, join } from "path";
|
|
1455
|
+
import { fileURLToPath } from "url";
|
|
1456
|
+
function ui(_cwd, args2) {
|
|
1457
|
+
const port = process.env.DOJO_UI_PORT ?? "4567";
|
|
1458
|
+
const uiDir = resolveUiDir();
|
|
1459
|
+
if (!uiDir) {
|
|
1460
|
+
console.error("Could not locate apps/ui/. The dojo UI currently requires running");
|
|
1461
|
+
console.error("`dojo` from inside (or near) the dojocho monorepo.");
|
|
1462
|
+
process.exit(1);
|
|
1463
|
+
}
|
|
1464
|
+
console.error(`\u2192 starting dojo UI from ${uiDir}`);
|
|
1465
|
+
console.error(` http://localhost:${port}
|
|
1466
|
+
`);
|
|
1467
|
+
const env = { ...process.env, PORT: port };
|
|
1468
|
+
const child = spawn("pnpm", ["dev", ...args2], {
|
|
1469
|
+
cwd: uiDir,
|
|
1470
|
+
stdio: "inherit",
|
|
1471
|
+
env
|
|
1472
|
+
});
|
|
1473
|
+
child.on("error", (err) => {
|
|
1474
|
+
if (err.code === "ENOENT") {
|
|
1475
|
+
console.error("Could not find `pnpm` on PATH.");
|
|
1476
|
+
console.error("Install pnpm: https://pnpm.io/installation");
|
|
1477
|
+
process.exit(127);
|
|
1478
|
+
}
|
|
1479
|
+
console.error(`Failed to launch UI: ${err.message}`);
|
|
1480
|
+
process.exit(1);
|
|
1481
|
+
});
|
|
1482
|
+
child.on("exit", (code, signal) => {
|
|
1483
|
+
if (signal) process.kill(process.pid, signal);
|
|
1484
|
+
else process.exit(code ?? 0);
|
|
1485
|
+
});
|
|
1486
|
+
}
|
|
1487
|
+
function resolveUiDir() {
|
|
1488
|
+
const here = fileURLToPath(import.meta.url);
|
|
1489
|
+
let dir = dirname4(here);
|
|
1490
|
+
for (let i = 0; i < 10; i++) {
|
|
1491
|
+
const candidate = join(dir, "apps", "ui");
|
|
1492
|
+
if (existsSync9(join(candidate, "package.json"))) return candidate;
|
|
1493
|
+
const parent = dirname4(dir);
|
|
1494
|
+
if (parent === dir) break;
|
|
1495
|
+
dir = parent;
|
|
1496
|
+
}
|
|
1497
|
+
return null;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1441
1500
|
// src/index.ts
|
|
1442
1501
|
var [command, ...args] = process.argv.slice(2);
|
|
1443
1502
|
async function main() {
|
|
@@ -1453,6 +1512,8 @@ async function main() {
|
|
|
1453
1512
|
remove(findProjectRoot(), args);
|
|
1454
1513
|
} else if (command === "status") {
|
|
1455
1514
|
status2(findProjectRoot(), args);
|
|
1515
|
+
} else if (command === "ui") {
|
|
1516
|
+
ui(process.cwd(), args);
|
|
1456
1517
|
} else {
|
|
1457
1518
|
root(process.cwd(), [command, ...args].filter(Boolean));
|
|
1458
1519
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dojocho",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Installable coding dojos that turn your AI agent into a sensei.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -35,18 +35,17 @@
|
|
|
35
35
|
"bin": {
|
|
36
36
|
"dojo": "./dist/index.js"
|
|
37
37
|
},
|
|
38
|
-
"scripts": {
|
|
39
|
-
"build": "tsup",
|
|
40
|
-
"test": "vitest run",
|
|
41
|
-
"test:watch": "vitest",
|
|
42
|
-
"prepublishOnly": "pnpm build"
|
|
43
|
-
},
|
|
44
38
|
"dependencies": {
|
|
45
|
-
"@dojocho/config": "
|
|
39
|
+
"@dojocho/config": "0.0.3"
|
|
46
40
|
},
|
|
47
41
|
"devDependencies": {
|
|
48
42
|
"tsup": "^8.0.0",
|
|
49
43
|
"typescript": "^5.7.0",
|
|
50
44
|
"vitest": "^4.0.18"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsup",
|
|
48
|
+
"test": "vitest run",
|
|
49
|
+
"test:watch": "vitest"
|
|
51
50
|
}
|
|
52
|
-
}
|
|
51
|
+
}
|