tod-ai 0.1.0 → 0.2.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.md +17 -3
- package/dist/cli.js +364 -58
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -18,6 +18,7 @@ tod installs a delimited instruction block into your agent's own global instruct
|
|
|
18
18
|
operator.md # what your agents know about you
|
|
19
19
|
work.json # projects, features, tasks
|
|
20
20
|
log.jsonl # append-only activity log
|
|
21
|
+
hints.json # which hint to show next
|
|
21
22
|
```
|
|
22
23
|
|
|
23
24
|
Everything outside tod's markers stays untouched, byte for byte. Agents update work state through the `tod` CLI, never by editing files, so the record stays trustworthy.
|
|
@@ -29,7 +30,7 @@ npm install -g tod-ai # or: bun add -g tod-ai
|
|
|
29
30
|
tod init
|
|
30
31
|
```
|
|
31
32
|
|
|
32
|
-
`tod init` asks nothing. Open your coding agent afterwards; it will introduce itself and get to know you in your first session.
|
|
33
|
+
`tod init` asks nothing. Open your coding agent afterwards; it will introduce itself and get to know you in your first session. From then on there is nothing to switch on: describe what you want built, changed, or fixed and your agent responds as Tod. Each session opens with a one-line hint on how to steer him, such as asking him to be less wordy.
|
|
33
34
|
|
|
34
35
|
## Commands
|
|
35
36
|
|
|
@@ -42,10 +43,22 @@ tod sync # repair tod-managed content after any damage
|
|
|
42
43
|
tod work # record and update features, bugs, tasks (agent-facing)
|
|
43
44
|
tod log # append to the activity log (agent-facing)
|
|
44
45
|
tod config # read or change settings such as communication style
|
|
46
|
+
tod skills # report tod's agent skills and how to install any missing (agent-facing)
|
|
47
|
+
tod hint # print the next one-line hint on steering Tod (agent-facing)
|
|
45
48
|
```
|
|
46
49
|
|
|
47
50
|
Every command is non-interactive and idempotent, refuses to write outside `~/.agents/`, `~/.claude/`, and `~/.tod/`, and reports exactly what it changed.
|
|
48
51
|
|
|
52
|
+
## The paved road
|
|
53
|
+
|
|
54
|
+
tod has opinions about how software gets built, not just about process. Those opinions ship as an agent skill rather than as behaviour in the CLI, because tod never writes into project folders: the skill tells your agent what to do, and your agent does it.
|
|
55
|
+
|
|
56
|
+
`tod init` reports whether the skill is installed and prints the command that installs it, pinned to the release tag matching your installed tod. Your agent runs that command; skill and CLI therefore never disagree.
|
|
57
|
+
|
|
58
|
+
When you ask for something new, the agent scaffolds it on a known-good stack (Bun, TypeScript, React with Vite, PostgreSQL with Prisma, Zod, Vitest, Playwright, Biome), gives the project two verification commands, and configures the compiler and linter so unsafe code fails a check rather than relying on the agent to remember a rule.
|
|
59
|
+
|
|
60
|
+
These are defaults, not constraints. Ask for something different and the agent tells you what it costs, then does it your way and records the decision so no later session re-argues it.
|
|
61
|
+
|
|
49
62
|
## Supported agents
|
|
50
63
|
|
|
51
64
|
Any agent that reads the global `~/.agents/AGENTS.md` works with zero configuration, including Codex CLI and opencode. Claude Code is supported through `~/.claude/CLAUDE.md`. Adding another agent is a one-line target in `src/paths.ts`.
|
|
@@ -54,10 +67,11 @@ Any agent that reads the global `~/.agents/AGENTS.md` works with zero configurat
|
|
|
54
67
|
|
|
55
68
|
```sh
|
|
56
69
|
bun install
|
|
57
|
-
bun run check
|
|
70
|
+
bun run check # fast loop: typecheck, lint, tests
|
|
71
|
+
bun run check:full # complete gate: adds build and dead-code analysis
|
|
58
72
|
```
|
|
59
73
|
|
|
60
|
-
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
74
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) and the [contributor documentation](docs/README.md) for how tod works, its core rules, and the templates and skills it ships.
|
|
61
75
|
|
|
62
76
|
## Licence
|
|
63
77
|
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
// @bun
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __export = (target, all) => {
|
|
@@ -10,6 +10,61 @@ var __export = (target, all) => {
|
|
|
10
10
|
set: (newValue) => all[name] = () => newValue
|
|
11
11
|
});
|
|
12
12
|
};
|
|
13
|
+
// package.json
|
|
14
|
+
var package_default = {
|
|
15
|
+
name: "tod-ai",
|
|
16
|
+
version: "0.2.0",
|
|
17
|
+
description: "An operator harness that turns any AGENTS.md-compatible coding agent into a software factory for non-technical builders.",
|
|
18
|
+
license: "MIT",
|
|
19
|
+
type: "module",
|
|
20
|
+
bin: {
|
|
21
|
+
tod: "./dist/cli.js"
|
|
22
|
+
},
|
|
23
|
+
files: [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
repository: {
|
|
27
|
+
type: "git",
|
|
28
|
+
url: "git+https://github.com/tylerhogarth/tod.git"
|
|
29
|
+
},
|
|
30
|
+
homepage: "https://github.com/tylerhogarth/tod#readme",
|
|
31
|
+
bugs: "https://github.com/tylerhogarth/tod/issues",
|
|
32
|
+
keywords: [
|
|
33
|
+
"agents",
|
|
34
|
+
"agents-md",
|
|
35
|
+
"claude-code",
|
|
36
|
+
"codex",
|
|
37
|
+
"coding-agents",
|
|
38
|
+
"operator",
|
|
39
|
+
"harness"
|
|
40
|
+
],
|
|
41
|
+
scripts: {
|
|
42
|
+
sandbox: "TOD_HOME=output bun src/cli.ts",
|
|
43
|
+
typecheck: "tsc --noEmit",
|
|
44
|
+
lint: "biome check .",
|
|
45
|
+
"lint:fix": "biome check --write .",
|
|
46
|
+
test: "bun test",
|
|
47
|
+
build: "bun run scripts/build.ts",
|
|
48
|
+
check: "bun run typecheck && bun run lint && bun test",
|
|
49
|
+
prepublishOnly: "bun run check:full",
|
|
50
|
+
knip: "knip-bun",
|
|
51
|
+
"check:full": "bun run check && bun run build && bun run knip"
|
|
52
|
+
},
|
|
53
|
+
engines: {
|
|
54
|
+
bun: ">=1.3.0",
|
|
55
|
+
node: ">=20"
|
|
56
|
+
},
|
|
57
|
+
dependencies: {
|
|
58
|
+
"better-result": "3.0.1",
|
|
59
|
+
zod: "4.5.4"
|
|
60
|
+
},
|
|
61
|
+
devDependencies: {
|
|
62
|
+
"@biomejs/biome": "2.5.11",
|
|
63
|
+
"@types/bun": "1.4.0",
|
|
64
|
+
knip: "6.34.0",
|
|
65
|
+
typescript: "7.0.2"
|
|
66
|
+
}
|
|
67
|
+
};
|
|
13
68
|
|
|
14
69
|
// src/boundary.ts
|
|
15
70
|
import { existsSync, realpathSync } from "node:fs";
|
|
@@ -30,7 +85,8 @@ function todPaths(home = resolveHome()) {
|
|
|
30
85
|
configFile: join(todDir, "config.json"),
|
|
31
86
|
operatorFile: join(todDir, "operator.md"),
|
|
32
87
|
workFile: join(todDir, "work.json"),
|
|
33
|
-
logFile: join(todDir, "log.jsonl")
|
|
88
|
+
logFile: join(todDir, "log.jsonl"),
|
|
89
|
+
hintFile: join(todDir, "hints.json")
|
|
34
90
|
};
|
|
35
91
|
}
|
|
36
92
|
function agentTargets(home = resolveHome()) {
|
|
@@ -19195,6 +19251,43 @@ fix: ${error61.fix}
|
|
|
19195
19251
|
`;
|
|
19196
19252
|
}
|
|
19197
19253
|
|
|
19254
|
+
// src/skills.ts
|
|
19255
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
19256
|
+
import { join as join3 } from "node:path";
|
|
19257
|
+
var SKILLS = [
|
|
19258
|
+
{
|
|
19259
|
+
name: "tod-create-project",
|
|
19260
|
+
summary: "Scaffold a new project on tod's engineering paved road, or bring an existing one onto it."
|
|
19261
|
+
}
|
|
19262
|
+
];
|
|
19263
|
+
function repositorySlug(url2) {
|
|
19264
|
+
const match = /github\.com[/:]([^/]+)\/(.+?)(?:\.git)?$/.exec(url2.trim());
|
|
19265
|
+
const owner = match?.[1];
|
|
19266
|
+
const name = match?.[2];
|
|
19267
|
+
return owner === undefined || name === undefined ? undefined : `${owner}/${name}`;
|
|
19268
|
+
}
|
|
19269
|
+
function skillTag(version2) {
|
|
19270
|
+
return `v${version2}`;
|
|
19271
|
+
}
|
|
19272
|
+
function installCommand(slug, version2, skill) {
|
|
19273
|
+
return `npx skills add ${slug}#${skillTag(version2)} --skill ${skill} -g -y`;
|
|
19274
|
+
}
|
|
19275
|
+
function isSkillInstalled(home, name) {
|
|
19276
|
+
return agentTargets(home).some((target) => existsSync2(join3(target.configDir, "skills", name, "SKILL.md")));
|
|
19277
|
+
}
|
|
19278
|
+
function skillStatuses(home) {
|
|
19279
|
+
const slug = repositorySlug(package_default.repository.url);
|
|
19280
|
+
return SKILLS.map((skill) => ({
|
|
19281
|
+
name: skill.name,
|
|
19282
|
+
summary: skill.summary,
|
|
19283
|
+
installed: isSkillInstalled(home, skill.name),
|
|
19284
|
+
command: slug === undefined ? undefined : installCommand(slug, package_default.version, skill.name)
|
|
19285
|
+
}));
|
|
19286
|
+
}
|
|
19287
|
+
function renderSkillLines(statuses) {
|
|
19288
|
+
return statuses.map((skill) => skill.installed ? `installed skill ${skill.name}` : `missing skill ${skill.name}: install with '${skill.command ?? "tod skills"}'`);
|
|
19289
|
+
}
|
|
19290
|
+
|
|
19198
19291
|
// src/commands/harness-io.ts
|
|
19199
19292
|
function tildify(path, home = resolveHome()) {
|
|
19200
19293
|
return path.startsWith(home) ? `~${path.slice(home.length)}` : path;
|
|
@@ -19241,6 +19334,7 @@ function renderReport(report, home, summary) {
|
|
|
19241
19334
|
for (const skipped of report.skippedAgents) {
|
|
19242
19335
|
lines.push(`skipped ${skipped}: config folder not found, no block installed`);
|
|
19243
19336
|
}
|
|
19337
|
+
lines.push(...renderSkillLines(skillStatuses(home)));
|
|
19244
19338
|
lines.push(summary);
|
|
19245
19339
|
return `${lines.join(`
|
|
19246
19340
|
`)}
|
|
@@ -19330,8 +19424,11 @@ usage:
|
|
|
19330
19424
|
}
|
|
19331
19425
|
};
|
|
19332
19426
|
|
|
19427
|
+
// src/hints.ts
|
|
19428
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
19429
|
+
|
|
19333
19430
|
// src/harness.ts
|
|
19334
|
-
import { existsSync as
|
|
19431
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
19335
19432
|
|
|
19336
19433
|
// src/markers.ts
|
|
19337
19434
|
var BLOCK_BEGIN = "<!-- tod:begin (managed by tod; do not edit between markers, run `tod sync` to repair) -->";
|
|
@@ -19381,11 +19478,11 @@ ${BLOCK_END}
|
|
|
19381
19478
|
|
|
19382
19479
|
// src/template.ts
|
|
19383
19480
|
var requirementGatheringLines = {
|
|
19384
|
-
1: "Ask at most one
|
|
19481
|
+
1: "Ask at most one question, make reasonable assumptions, and start building. State the assumptions you acted on.",
|
|
19385
19482
|
2: "Ask one or two questions on the points that most change the outcome, then build on stated assumptions.",
|
|
19386
19483
|
3: "Confirm goal, scope, and constraints in a short exchange before building.",
|
|
19387
|
-
4: "Explore
|
|
19388
|
-
5: "
|
|
19484
|
+
4: "Explore goal, users, scope, and edge cases with the operator before building. Play back your understanding and confirm it.",
|
|
19485
|
+
5: "Agree goal, users, scope, edge cases, and what done looks like before any implementation. Involve the operator in the choices and agree the plan first."
|
|
19389
19486
|
};
|
|
19390
19487
|
var responseDetailLines = {
|
|
19391
19488
|
1: "Report the outcome and the next step in a few sentences. Skip mechanism and background.",
|
|
@@ -19394,68 +19491,100 @@ var responseDetailLines = {
|
|
|
19394
19491
|
4: "Explain what happened, the decisions you took, and the reasoning behind them.",
|
|
19395
19492
|
5: "Explain what happened, why each decision was made, and how the result works."
|
|
19396
19493
|
};
|
|
19397
|
-
|
|
19398
|
-
return `# tod: operator harness
|
|
19399
|
-
|
|
19400
|
-
tod is an operator harness layered on top of you, the coding agent. The operator is a builder creating real software through you. This block is an operating layer, not documentation: when the operator asks for software work, handle it through tod, and check whether a tod CLI command or tod skill applies before acting. Run \`tod --help\` for your commands. Shared state lives in \`~/.tod/\`; the CLI is for you, not the operator.
|
|
19401
|
-
|
|
19402
|
-
## Tod persona
|
|
19403
|
-
|
|
19404
|
-
- Tod is a lively product engineer with experience in product, engineering, and architecture.
|
|
19405
|
-
- He is concise and friendly.
|
|
19406
|
-
- When you are acting on behalf of tod, affect this persona and interact as Tod.
|
|
19407
|
-
|
|
19408
|
-
## Session start
|
|
19409
|
-
|
|
19410
|
-
- Ask the operator whether they want to use tod, for example: "Are we building with Tod today?". If yes, follow this block for the session. If no, set this block aside and operate normally; the operator may want you for something other than building software.
|
|
19411
|
-
- When tod is active, read \`~/.tod/operator.md\` before other work.
|
|
19494
|
+
var intro = `# tod: operator harness
|
|
19412
19495
|
|
|
19413
|
-
|
|
19496
|
+
You are the coding agent. tod is an operating model layered over you so a non-technical operator can build real software through you. This block is instructions, not documentation: handle software work through tod, and check for a tod command or skill before acting. Run \`tod --help\` for commands. State lives in \`~/.tod/\`; the CLI is for you, not the operator.`;
|
|
19497
|
+
var tod = `## Tod
|
|
19414
19498
|
|
|
19415
|
-
-
|
|
19499
|
+
- Tod is the operator's whole team in one: product, engineering, and delivery. Speak as Tod: concise, friendly, direct.
|
|
19500
|
+
- Product: what users get and why it matters to them. This decides what to build.
|
|
19501
|
+
- Engineering: design, architecture, and implementation. Every choice is a trade-off, not a right answer. Choose, state the trade-off in product terms, and move on.
|
|
19502
|
+
- Delivery: ship in slices. Each session ends with something useful the operator can use. Time is not a dimension; scope is. Delivery fails through growing scope and parallel streams of work, not lateness. Hold scope, finish the slice, deliver every session.
|
|
19503
|
+
- The three compete. Product decides what, engineering decides how, delivery decides how much now.`;
|
|
19504
|
+
var sessionStart = `## Session start
|
|
19416
19505
|
|
|
19417
|
-
|
|
19506
|
+
- Never ask whether the operator wants tod. When they describe product work (a feature, a bug, a question about their software or what is in progress), respond as Tod from that message on. For anything else, work normally and stay out of the way.
|
|
19507
|
+
- When Tod becomes active: read \`~/.tod/operator.md\`, run \`tod hint\`, and open your first reply with the line it prints, verbatim.`;
|
|
19508
|
+
var precedence = `## Precedence
|
|
19418
19509
|
|
|
19419
|
-
-
|
|
19420
|
-
|
|
19421
|
-
- Supply the product and engineering judgement the operator lacks. Make the engineering calls yourself; surface a decision only when it changes what the product does for its users. Present one voice.
|
|
19510
|
+
- Operator and project instructions that apply to the task take precedence over this block.`;
|
|
19511
|
+
var nonTechnicalOperator = `## The operator is non-technical
|
|
19422
19512
|
|
|
19423
|
-
|
|
19513
|
+
- Assume this at every setting below.
|
|
19514
|
+
- Explain decisions as consequences, trade-offs, and product impact. No jargon or implementation detail unless asked.
|
|
19515
|
+
- Make the engineering calls yourself. Surface a decision only when it changes what the product does for its users. Present one voice.
|
|
19516
|
+
- When a technical term is unavoidable, define it in one plain sentence. Keep file paths, tool names, and internals out of replies unless asked.`;
|
|
19517
|
+
function requirementGathering(config3) {
|
|
19518
|
+
return `## Requirement gathering (set to ${config3.requirementGathering} of 5)
|
|
19424
19519
|
|
|
19425
19520
|
- ${requirementGatheringLines[config3.requirementGathering]}
|
|
19521
|
+
- Pair each question with a recommendation the operator can react to.`;
|
|
19522
|
+
}
|
|
19523
|
+
function responseDetail(config3) {
|
|
19524
|
+
return `## Response detail (set to ${config3.responseDetail} of 5)
|
|
19426
19525
|
|
|
19427
|
-
|
|
19428
|
-
|
|
19429
|
-
|
|
19430
|
-
|
|
19431
|
-
## Reconfiguration
|
|
19526
|
+
- ${responseDetailLines[config3.responseDetail]}`;
|
|
19527
|
+
}
|
|
19528
|
+
var reconfiguration = `## Reconfiguration
|
|
19432
19529
|
|
|
19433
|
-
-
|
|
19434
|
-
-
|
|
19530
|
+
- A direct instruction ("Tod, be less wordy", "Tod, tell me more", "Tod, just build it", "Tod, ask me before you decide") is applied at once with \`tod config set\` and \`tod sync\`. Confirm in one line.
|
|
19531
|
+
- When the operator keeps working against a setting without saying so, offer to adjust it or to run \`tod init\` again. Never change \`~/.tod/config.json\` from inferred behaviour alone.`;
|
|
19532
|
+
var decideByRisk = `## Decide by risk
|
|
19435
19533
|
|
|
19436
|
-
|
|
19534
|
+
- Reversible work (new screens, content, styling, additions): build it, then show the result.
|
|
19535
|
+
- Hard-to-reverse work (deleting data, changing existing behaviour, anything touching money, accounts, or privacy): explain the consequence in plain language and wait for a yes.
|
|
19536
|
+
- Unsure which applies: treat it as risky.`;
|
|
19537
|
+
var sliceTheWork = `## Slice the work
|
|
19437
19538
|
|
|
19438
|
-
-
|
|
19439
|
-
-
|
|
19440
|
-
-
|
|
19441
|
-
|
|
19539
|
+
- One finishable slice at a time. For a large request, propose a short ordered list of slices and ask which to start.
|
|
19540
|
+
- Every slice ends in something the operator can see and check.
|
|
19541
|
+
- Scope creep is the failure mode. When a request grows mid-slice, finish the slice, then propose the growth as the next one.`;
|
|
19542
|
+
var showYourWork = `## Show your work
|
|
19442
19543
|
|
|
19443
|
-
|
|
19544
|
+
- Nothing is done without evidence the operator can check without reading code.
|
|
19545
|
+
- Interface change: say what to look at and run the app. Data change: show before and after. Invisible change: state the outcome and show its check passing.`;
|
|
19546
|
+
var writingStyle = `## Writing style
|
|
19444
19547
|
|
|
19445
|
-
-
|
|
19446
|
-
-
|
|
19548
|
+
- Lead with the answer or the action. One idea per sentence. Fact before reason.
|
|
19549
|
+
- Never use em dashes; use a colon, a comma, or two sentences. No en dashes or double hyphens either.
|
|
19550
|
+
- Plain verbs, international English, no filler or intensifiers.
|
|
19551
|
+
- Three or more items: numbered list. A new issue goes at the end, never mid-answer.`;
|
|
19552
|
+
var workTracking = `## Work tracking
|
|
19447
19553
|
|
|
19448
|
-
|
|
19554
|
+
- Record work with the CLI, never by editing files: \`tod work\` for features, bugs, and tasks; \`tod log\` for notable events; \`tod status\` for "what am I working on?".
|
|
19555
|
+
- Record a feature when it starts; mark items done as they finish. Work state is the operator's memory of what is in flight, so keep it truthful.`;
|
|
19556
|
+
var gitSafety = `## Git safety
|
|
19449
19557
|
|
|
19450
|
-
- Every feature and every
|
|
19451
|
-
-
|
|
19452
|
-
- You own git
|
|
19558
|
+
- Every feature and every fix gets its own branch. Never develop directly on main.
|
|
19559
|
+
- Describe branches as separate versions of the operator's app. Never require git vocabulary from them.
|
|
19560
|
+
- You own git. When something goes wrong, fix it and explain what happened in plain terms.
|
|
19561
|
+
- Commit each working slice. When a slice goes wrong, roll back to the last good commit.`;
|
|
19562
|
+
var todManagedFiles = `## tod-managed files
|
|
19453
19563
|
|
|
19454
|
-
|
|
19564
|
+
- Never edit between tod's markers in this file; run \`tod sync\` if the block looks wrong.
|
|
19565
|
+
- Never hand-edit \`~/.tod/work.json\`, \`~/.tod/log.jsonl\`, \`~/.tod/config.json\`, or \`~/.tod/hints.json\`; use \`tod work\`, \`tod log\`, \`tod config\`, and \`tod hint\`.
|
|
19566
|
+
- \`~/.tod/operator.md\` is the one file you edit directly: record durable operator preferences and corrections there as you learn them.`;
|
|
19567
|
+
function renderBlock(config3) {
|
|
19568
|
+
const sections = [
|
|
19569
|
+
intro,
|
|
19570
|
+
tod,
|
|
19571
|
+
sessionStart,
|
|
19572
|
+
precedence,
|
|
19573
|
+
nonTechnicalOperator,
|
|
19574
|
+
requirementGathering(config3),
|
|
19575
|
+
responseDetail(config3),
|
|
19576
|
+
reconfiguration,
|
|
19577
|
+
decideByRisk,
|
|
19578
|
+
sliceTheWork,
|
|
19579
|
+
showYourWork,
|
|
19580
|
+
writingStyle,
|
|
19581
|
+
workTracking,
|
|
19582
|
+
gitSafety,
|
|
19583
|
+
todManagedFiles
|
|
19584
|
+
];
|
|
19585
|
+
return `${sections.join(`
|
|
19455
19586
|
|
|
19456
|
-
|
|
19457
|
-
- Never hand-edit \`~/.tod/work.json\`, \`~/.tod/log.jsonl\`, or \`~/.tod/config.json\`; use \`tod work\`, \`tod log\`, and \`tod config\`.
|
|
19458
|
-
- \`~/.tod/operator.md\` is the one tod-managed file you edit directly: record durable operator preferences and corrections there as you learn them.
|
|
19587
|
+
`)}
|
|
19459
19588
|
`;
|
|
19460
19589
|
}
|
|
19461
19590
|
|
|
@@ -19476,7 +19605,7 @@ class NotInitialisedError extends h("NotInitialised") {
|
|
|
19476
19605
|
function installHarness(home, mode) {
|
|
19477
19606
|
const paths = todPaths(home);
|
|
19478
19607
|
const roots = defaultAllowedRoots(home);
|
|
19479
|
-
if (mode === "sync" && !
|
|
19608
|
+
if (mode === "sync" && !existsSync3(paths.todDir)) {
|
|
19480
19609
|
return ae.err(new NotInitialisedError({ todDir: paths.todDir }));
|
|
19481
19610
|
}
|
|
19482
19611
|
const configResult = loadConfig(paths.configFile);
|
|
@@ -19497,7 +19626,7 @@ function installHarness(home, mode) {
|
|
|
19497
19626
|
}
|
|
19498
19627
|
const skippedAgents = [];
|
|
19499
19628
|
for (const target of agentTargets(home)) {
|
|
19500
|
-
if (!
|
|
19629
|
+
if (!existsSync3(target.configDir)) {
|
|
19501
19630
|
skippedAgents.push(target.name);
|
|
19502
19631
|
continue;
|
|
19503
19632
|
}
|
|
@@ -19523,8 +19652,124 @@ function installHarness(home, mode) {
|
|
|
19523
19652
|
return ae.ok({ config: config3, files, skippedAgents });
|
|
19524
19653
|
}
|
|
19525
19654
|
|
|
19655
|
+
// src/hints.ts
|
|
19656
|
+
var HINTS = [
|
|
19657
|
+
'Tod too wordy? Say "Tod, be less wordy" at any point.',
|
|
19658
|
+
'Want to know more about what was built? Say "Tod, tell me more."',
|
|
19659
|
+
'Tod asking too many questions? Say "Tod, just build it."',
|
|
19660
|
+
'Want Tod to check with you more? Say "Tod, ask me before you decide."',
|
|
19661
|
+
'Not sure what is in progress? Ask "Tod, what am I working on?"',
|
|
19662
|
+
"Something you always want done a certain way? Tell Tod once and he remembers.",
|
|
19663
|
+
'Want to see it working? Say "Tod, show me."',
|
|
19664
|
+
'Something went wrong with the latest change? Say "Tod, go back to the last version that worked."',
|
|
19665
|
+
"Starting something new? Describe what it should do and Tod sets the project up for you.",
|
|
19666
|
+
"Something broke? Describe what you saw and Tod tracks it as a bug until it is fixed.",
|
|
19667
|
+
"Not building today? Ask for whatever you need and Tod stays out of the way."
|
|
19668
|
+
];
|
|
19669
|
+
var hintStateSchema = exports_external.object({
|
|
19670
|
+
version: exports_external.literal(1),
|
|
19671
|
+
next: exports_external.number().int().nonnegative()
|
|
19672
|
+
});
|
|
19673
|
+
|
|
19674
|
+
class HintStateError extends h("HintState") {
|
|
19675
|
+
}
|
|
19676
|
+
function loadHintState(path) {
|
|
19677
|
+
const raw = readFileIfExists(path);
|
|
19678
|
+
if (raw === null) {
|
|
19679
|
+
return ae.ok({ version: 1, next: 0 });
|
|
19680
|
+
}
|
|
19681
|
+
let parsed;
|
|
19682
|
+
try {
|
|
19683
|
+
parsed = JSON.parse(raw);
|
|
19684
|
+
} catch (cause) {
|
|
19685
|
+
return ae.err(new HintStateError({
|
|
19686
|
+
path,
|
|
19687
|
+
message: `not valid JSON: ${cause instanceof Error ? cause.message : String(cause)}`
|
|
19688
|
+
}));
|
|
19689
|
+
}
|
|
19690
|
+
const checked = hintStateSchema.safeParse(parsed);
|
|
19691
|
+
if (!checked.success) {
|
|
19692
|
+
return ae.err(new HintStateError({ path, message: exports_external.prettifyError(checked.error) }));
|
|
19693
|
+
}
|
|
19694
|
+
return ae.ok(checked.data);
|
|
19695
|
+
}
|
|
19696
|
+
function formatHint(hint) {
|
|
19697
|
+
return `_Hint: ${hint}_`;
|
|
19698
|
+
}
|
|
19699
|
+
function nextHint(paths, roots) {
|
|
19700
|
+
if (!existsSync4(paths.configFile)) {
|
|
19701
|
+
return ae.err(new NotInitialisedError({ todDir: paths.todDir }));
|
|
19702
|
+
}
|
|
19703
|
+
const path = paths.hintFile;
|
|
19704
|
+
const loaded = loadHintState(path);
|
|
19705
|
+
if (loaded.isErr()) {
|
|
19706
|
+
return ae.err(loaded.error);
|
|
19707
|
+
}
|
|
19708
|
+
const index = loaded.value.next % HINTS.length;
|
|
19709
|
+
const hint = HINTS[index];
|
|
19710
|
+
if (hint === undefined) {
|
|
19711
|
+
return ae.err(new HintStateError({ path, message: "no hints are defined" }));
|
|
19712
|
+
}
|
|
19713
|
+
const state = { version: 1, next: (index + 1) % HINTS.length };
|
|
19714
|
+
const written = writeFileAtomic(path, `${JSON.stringify(state, null, 2)}
|
|
19715
|
+
`, roots);
|
|
19716
|
+
if (written.isErr()) {
|
|
19717
|
+
return ae.err(written.error);
|
|
19718
|
+
}
|
|
19719
|
+
return ae.ok(hint);
|
|
19720
|
+
}
|
|
19721
|
+
|
|
19722
|
+
// src/commands/hint.ts
|
|
19723
|
+
var hint = {
|
|
19724
|
+
help: `tod hint: print the next operator hint
|
|
19725
|
+
|
|
19726
|
+
Use once at the start of a session when tod is active, and show the printed
|
|
19727
|
+
line to the operator verbatim as the opening line of your first reply. Hints
|
|
19728
|
+
remind the operator that they can steer Tod in plain language. Each call
|
|
19729
|
+
advances to the next hint and wraps at the end, so the operator sees them in
|
|
19730
|
+
turn. Writes only the hint cursor in ~/.tod/. Requires 'tod init' first.
|
|
19731
|
+
`,
|
|
19732
|
+
execute: async (args) => {
|
|
19733
|
+
const unknown2 = args.find((arg) => arg.startsWith("-"));
|
|
19734
|
+
if (unknown2 !== undefined) {
|
|
19735
|
+
process.stderr.write(formatError2({
|
|
19736
|
+
what: `unknown flag '${unknown2}'`,
|
|
19737
|
+
why: "tod hint takes no flags",
|
|
19738
|
+
fix: "run 'tod hint' with no arguments"
|
|
19739
|
+
}));
|
|
19740
|
+
return EXIT.usage;
|
|
19741
|
+
}
|
|
19742
|
+
const home = resolveHome();
|
|
19743
|
+
const paths = todPaths(home);
|
|
19744
|
+
const result = nextHint(paths, defaultAllowedRoots(home));
|
|
19745
|
+
return result.match({
|
|
19746
|
+
ok: (text) => {
|
|
19747
|
+
process.stdout.write(`${formatHint(text)}
|
|
19748
|
+
`);
|
|
19749
|
+
return EXIT.ok;
|
|
19750
|
+
},
|
|
19751
|
+
err: (error61) => {
|
|
19752
|
+
if (error61._tag === "HintState") {
|
|
19753
|
+
process.stderr.write(formatError2({
|
|
19754
|
+
what: "hint state is unreadable",
|
|
19755
|
+
why: `${tildify(error61.path, home)}: ${error61.message}`,
|
|
19756
|
+
fix: `delete ${tildify(error61.path, home)} (the hint cycle restarts from the first hint), then re-run`
|
|
19757
|
+
}));
|
|
19758
|
+
return EXIT.failure;
|
|
19759
|
+
}
|
|
19760
|
+
process.stderr.write(formatError2(harnessErrorToAgentError(error61, home)));
|
|
19761
|
+
return EXIT.failure;
|
|
19762
|
+
}
|
|
19763
|
+
});
|
|
19764
|
+
}
|
|
19765
|
+
};
|
|
19766
|
+
|
|
19526
19767
|
// src/commands/init.ts
|
|
19527
19768
|
var ONBOARDING = `
|
|
19769
|
+
skills: if the report above lists a missing skill, run the install command it
|
|
19770
|
+
shows now, before onboarding. The skill carries tod's engineering defaults and
|
|
19771
|
+
routes itself once installed.
|
|
19772
|
+
|
|
19528
19773
|
onboarding: deliver this script to the operator as Tod, verbatim, as a single
|
|
19529
19774
|
message with both questions. Wait for both answers before recording.
|
|
19530
19775
|
|
|
@@ -19549,7 +19794,8 @@ var init = {
|
|
|
19549
19794
|
Use for first-time setup and whenever the operator wants to reconfigure tod.
|
|
19550
19795
|
Creates ~/.tod/ state if missing and appends tod's delimited instruction
|
|
19551
19796
|
block to each detected agent's global instruction file (~/.agents/AGENTS.md,
|
|
19552
|
-
~/.claude/CLAUDE.md); content outside tod's block is never modified.
|
|
19797
|
+
~/.claude/CLAUDE.md); content outside tod's block is never modified. Reports
|
|
19798
|
+
whether each tod skill is installed and the command to install it. Every
|
|
19553
19799
|
run ends with the two-question onboarding wizard for you to conduct with the
|
|
19554
19800
|
operator; record the answers with 'tod config set', then run 'tod sync'.
|
|
19555
19801
|
Idempotent: re-running repairs rather than duplicates.
|
|
@@ -19631,6 +19877,59 @@ usage: tod log <message> [--project <name>]
|
|
|
19631
19877
|
}
|
|
19632
19878
|
};
|
|
19633
19879
|
|
|
19880
|
+
// src/commands/skills.ts
|
|
19881
|
+
function render(version2, statuses) {
|
|
19882
|
+
const missing = statuses.filter((skill) => !skill.installed);
|
|
19883
|
+
const lines = [`tod ${version2} skills, pinned to tag ${skillTag(version2)}.`, ""];
|
|
19884
|
+
if (missing.length === 0) {
|
|
19885
|
+
lines.push("All tod skills are installed; nothing to do.");
|
|
19886
|
+
} else {
|
|
19887
|
+
lines.push("Install the missing skills with:", "");
|
|
19888
|
+
for (const skill of missing) {
|
|
19889
|
+
lines.push(` ${skill.command}`);
|
|
19890
|
+
}
|
|
19891
|
+
}
|
|
19892
|
+
lines.push("", "skills:");
|
|
19893
|
+
for (const skill of statuses) {
|
|
19894
|
+
lines.push(` ${skill.installed ? "installed" : "missing "} ${skill.name} ${skill.summary}`);
|
|
19895
|
+
}
|
|
19896
|
+
lines.push("", "The tag pins the skill to this tod version, so the two never disagree.", "This command prints only: it installs nothing and writes nothing.", "If the install fails on an unresolved tag, the release was not tagged; report", "that rather than installing from a branch.");
|
|
19897
|
+
return `${lines.join(`
|
|
19898
|
+
`)}
|
|
19899
|
+
`;
|
|
19900
|
+
}
|
|
19901
|
+
var skills = {
|
|
19902
|
+
help: `tod skills: report tod's agent skills and how to install any that are missing
|
|
19903
|
+
|
|
19904
|
+
Use when 'tod init' or 'tod sync' reports a missing skill, or when you are
|
|
19905
|
+
unsure whether a tod skill is installed. Prints an install command pinned to
|
|
19906
|
+
the tag matching this tod version, so the skill and the CLI agree. Installs
|
|
19907
|
+
nothing, writes nothing, and makes no network request.
|
|
19908
|
+
`,
|
|
19909
|
+
execute: async (args) => {
|
|
19910
|
+
const unknown2 = args.find((arg) => arg.startsWith("-"));
|
|
19911
|
+
if (unknown2 !== undefined) {
|
|
19912
|
+
process.stderr.write(formatError2({
|
|
19913
|
+
what: `unknown flag '${unknown2}'`,
|
|
19914
|
+
why: "tod skills takes no flags",
|
|
19915
|
+
fix: "run 'tod skills' with no arguments"
|
|
19916
|
+
}));
|
|
19917
|
+
return EXIT.usage;
|
|
19918
|
+
}
|
|
19919
|
+
const statuses = skillStatuses(resolveHome());
|
|
19920
|
+
if (statuses.some((skill) => skill.command === undefined)) {
|
|
19921
|
+
process.stderr.write(formatError2({
|
|
19922
|
+
what: "cannot determine the tod repository",
|
|
19923
|
+
why: `package metadata does not contain a recognisable GitHub URL: '${package_default.repository.url}'`,
|
|
19924
|
+
fix: "report this as a tod bug; do not guess a repository to install skills from"
|
|
19925
|
+
}));
|
|
19926
|
+
return EXIT.failure;
|
|
19927
|
+
}
|
|
19928
|
+
process.stdout.write(render(package_default.version, statuses));
|
|
19929
|
+
return EXIT.ok;
|
|
19930
|
+
}
|
|
19931
|
+
};
|
|
19932
|
+
|
|
19634
19933
|
// src/work.ts
|
|
19635
19934
|
var workItemSchema = exports_external.object({
|
|
19636
19935
|
id: exports_external.number().int().positive(),
|
|
@@ -19775,7 +20074,8 @@ var sync = {
|
|
|
19775
20074
|
Use after tod-managed content was edited, deleted, or looks wrong, or after
|
|
19776
20075
|
changing settings with 'tod config'. Only touches content inside tod's own
|
|
19777
20076
|
marker blocks and tod-owned structural files; operator memory, work state,
|
|
19778
|
-
the log, and all content outside the markers are never modified.
|
|
20077
|
+
the log, and all content outside the markers are never modified. Reports any
|
|
20078
|
+
missing tod skill with its install command. Idempotent.
|
|
19779
20079
|
`,
|
|
19780
20080
|
execute: async () => {
|
|
19781
20081
|
const home = resolveHome();
|
|
@@ -19948,11 +20248,13 @@ var commands = {
|
|
|
19948
20248
|
status,
|
|
19949
20249
|
work,
|
|
19950
20250
|
log,
|
|
19951
|
-
config: config2
|
|
20251
|
+
config: config2,
|
|
20252
|
+
skills,
|
|
20253
|
+
hint
|
|
19952
20254
|
};
|
|
19953
20255
|
|
|
19954
20256
|
// src/run.ts
|
|
19955
|
-
var VERSION =
|
|
20257
|
+
var VERSION = package_default.version;
|
|
19956
20258
|
var HELP2 = `tod ${VERSION}: operator harness for coding agents
|
|
19957
20259
|
|
|
19958
20260
|
This CLI is built to be run by coding agents on behalf of a non-technical
|
|
@@ -19974,6 +20276,10 @@ commands:
|
|
|
19974
20276
|
log Append a notable event to the activity log. Append-only.
|
|
19975
20277
|
config Read or change the requirement-gathering and response-detail
|
|
19976
20278
|
settings; follow with 'tod sync' to apply.
|
|
20279
|
+
skills Report which tod skills are installed and print the pinned install
|
|
20280
|
+
command for any that are missing.
|
|
20281
|
+
hint Print the next operator hint. Use once at session start and show
|
|
20282
|
+
it verbatim; it reminds the operator how to steer Tod.
|
|
19977
20283
|
|
|
19978
20284
|
Run 'tod <command> --help' for when-to-use guidance and flags.
|
|
19979
20285
|
exit codes: 0 success · 1 failure (the message states the fix) · 2 usage error
|
|
@@ -19994,7 +20300,7 @@ async function run(argv) {
|
|
|
19994
20300
|
process.stderr.write(formatError2({
|
|
19995
20301
|
what: `unknown command '${name}'`,
|
|
19996
20302
|
why: `tod has no command named '${name}'`,
|
|
19997
|
-
fix: "run 'tod --help' and pick one of: init, sync, status, work, log, config"
|
|
20303
|
+
fix: "run 'tod --help' and pick one of: init, sync, status, work, log, config, skills, hint"
|
|
19998
20304
|
}));
|
|
19999
20305
|
return EXIT.usage;
|
|
20000
20306
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tod-ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "An operator harness that turns any AGENTS.md-compatible coding agent into a software factory for non-technical builders.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -33,7 +33,9 @@
|
|
|
33
33
|
"test": "bun test",
|
|
34
34
|
"build": "bun run scripts/build.ts",
|
|
35
35
|
"check": "bun run typecheck && bun run lint && bun test",
|
|
36
|
-
"prepublishOnly": "bun run check
|
|
36
|
+
"prepublishOnly": "bun run check:full",
|
|
37
|
+
"knip": "knip-bun",
|
|
38
|
+
"check:full": "bun run check && bun run build && bun run knip"
|
|
37
39
|
},
|
|
38
40
|
"engines": {
|
|
39
41
|
"bun": ">=1.3.0",
|
|
@@ -46,6 +48,7 @@
|
|
|
46
48
|
"devDependencies": {
|
|
47
49
|
"@biomejs/biome": "2.5.11",
|
|
48
50
|
"@types/bun": "1.4.0",
|
|
51
|
+
"knip": "6.34.0",
|
|
49
52
|
"typescript": "7.0.2"
|
|
50
53
|
}
|
|
51
54
|
}
|