@atindo23/tamo
v0.3.0
Published
`tamo` remembers reusable project setup so you do not have to rebuild or re-explain the same setup every time.
Readme
tamo
tamo remembers reusable project setup so you do not have to rebuild or re-explain the same setup every time.
It helps set up ordinary projects without taking ownership of them. Native project files stay authoritative, and the project does not depend on tamo afterward.
Install
CLI
npm install -g @atindo23/tamo
tamo --helpAgent skill
The coding-agent skill teaches compatible agents how to discover, save, and reuse Recipes with tamo.
npx skills add athif23/tamo --skill tamo # project-level
npx skills add athif23/tamo --skill tamo -g # globalInstalling the skill does not install the CLI itself.
Quick start
With an agent
Once the skill is installed, you can describe what you want without naming a Recipe first:
create a web app using TanStack Start, TypeScript, Tailwind, and shadcn
The agent can discover matching saved Recipes before rebuilding the same setup.
When you want to keep a setup for later:
i like this setup, save it with tamo
The agent can curate the reusable parts into a Recipe while leaving the source project untouched.
Create from a Recipe
tamo create my-app --recipe web --dry-runReview the plan, then apply it:
tamo create my-app --recipe web --yesSave reusable setup
tamo pack web --cwd ./my-project \
--include tsconfig.json \
--include src/styles.css \
--dry-runThen save it:
tamo pack web --cwd ./my-project \
--include tsconfig.json \
--include src/styles.css \
--yesAdd setup to an existing project
tamo add lint --cwd ./my-project --dry-runInspect a project
tamo inspect --cwd ./my-projectinspect walks nested directories too, so a repository containing several apps or services reports the setup artifacts at their own paths — apps/web/vite.config.ts, apps/desktop/Cargo.toml, services/api/pyproject.toml — without classifying the repository as any particular kind of project.
Every mutating command supports --dry-run, so you can review what tamo plans to do before anything changes.
Why tamo
Project setup is repetitive. The same framework choices, TypeScript settings, lint rules, UI setup, test tooling, and directory conventions often get rebuilt or re-explained from project to project.
tamo lets those decisions live as reusable Recipes.
The key idea is:
Reusable setup without tool ownership.
A Recipe can be used to create a new project or reconciled into an existing one. The result stays an ordinary project with ordinary native files.
Templates
Templates and starter repos are useful when you want to copy a known starting point.
template
↓ copy / render
new projectA Recipe is different. It represents reusable setup that can be planned against the project that already exists.
Recipe
↓ plan + reconcile
new or existing projectRecipes can also compose. Where tamo understands the artifact type, compatible contributions combine and incompatible intent blocks instead of silently picking a winner.
A template is still the simpler choice when a fixed copied starting point is exactly what you want.
Projen
Projen-style generators keep a generator definition as the source of truth and generate project files from it.
generator definition
↓
generated project filestamo deliberately keeps the native project files as the source of truth.
native project files
↕
reusable Recipe
↓
native project filestamo applies setup when asked, then gets out of the way. It does not continuously own or regenerate the project afterward.
Use a generator when you want centralized ownership of project configuration. Use tamo when you want reusable setup without handing ownership of the project to the setup tool.
Coding agents are one useful way to drive tamo, but they are not required. The CLI works on its own.
Recipes
A Recipe is reusable setup expressed through ordinary native project files plus a small amount of composition metadata.
Recipes live under the tamo home directory, which defaults to ~/.tamo and can be changed with TAMO_HOME.
~/.tamo/
recipes/
web/
recipe.json
behavior.mjs # optional default behavior entrypoint
artifacts/
package.json
tsconfig.json
...Artifacts
artifacts/ contains ordinary native project files. These are the files the underlying tools already understand, such as package.json, tsconfig.json, components.json, Cargo.toml, or any tool-specific config.
The project itself does not get a project-level tamo.json.
recipe.json
recipe.json contains Recipe composition metadata, such as included Recipes, persistent customizations, and an optional Behavior entrypoint.
Example:
{
"includes": [
{ "recipe": "typescript" },
{ "recipe": "lint" }
]
}A Recipe may also choose a custom relative .mjs Behavior entrypoint:
{
"behavior": "scripts/setup.mjs"
}If no explicit path is configured, behavior.mjs is the zero-config default.
Behavior
Behavior is optional trusted local JavaScript for procedural setup that native files alone cannot express, such as running an upstream initializer.
Most Recipes do not need it.
Behavior can participate around artifact application through three hooks:
prepare
↓
apply artifacts
↓
finalize
↓
verifyBehavior is trusted executable code and is not currently sandboxed.
Commands
Run tamo <command> --help for exact flags.
pack
pack saves selected native artifacts from an existing directory as a Recipe.
tamo pack web --cwd ./my-project \
--include tsconfig.json \
--include src \
--dry-runA root package.json, when present, is captured with package-manifest semantics. Nested manifests selected via --include keep their own name and version.
pack is package-manager agnostic: any valid package.json is packable regardless of the packageManager field or lockfiles present.
A package.json is optional. A directory without one can be packed from --include artifacts alone — pack never synthesizes one — and at least one artifact is required.
The root source name and version are not treated as reusable setup. packageManager is preserved when present and is never invented.
Secrets, private keys, lockfiles, caches, dependency directories, generated output, and symlinked source state are not captured. Naming one directly with --include is blocked rather than quietly capturing nothing.
--include of a directory expands it recursively, and that expansion skips known dependency, generated, cache, and VCS state at any depth (node_modules, dist, build, target, __pycache__, virtualenvs, tool caches, .git, and the rest of the same list). A project that has already been installed and built therefore packs without deleting anything first:
tamo pack web --cwd ./my-project \
--include apps \
--include packages \
--include servicesThe source project is never modified.
Repacking over an existing Recipe requires --force.
create
create applies a Recipe to a new project.
tamo create my-app --recipe web --dry-runReview the plan, then apply it, or materialize the files without installing:
tamo create my-app --recipe web --no-install --yesNative artifacts are written to their normal relative paths. A root package.json takes the new target directory's package name and plans a visible install by default; the manager comes from its packageManager field (pnpm, npm, yarn, or bun; pnpm when the field is absent). Nested manifests keep their own names and never trigger installs, and known workspace membership never triggers installs either. An unmatched nested manifest does not install either, and absence of membership never marks it independent. Tamo runs the root package manager once; that manager determines its own workspace behavior — tamo does not promise the root install covers any specific member. A Recipe with no root package.json creates without any install step. --no-install skips the automatic install while still creating and verifying the files.
Targets that already exist and are non-empty are blocked instead of overwritten.
The resulting project contains no tamo metadata.
add
add reconciles a Recipe with an existing project.
tamo add lint --cwd ./my-project --dry-runCompatible state is preserved or combined, identical state becomes a no-op, missing state is added, and conflicting intent blocks instead of silently overwriting existing setup.
Rerunning a settled Recipe is idempotent.
inspect
inspect reports setup detected in an existing project and does not modify it.
tamo inspect --cwd ./my-projectIt reports root package facts plus a recursive inventory of setup artifacts — each at its project-relative path, with the handler that understands it or whole-file for artifacts carried with conservative whole-file semantics. package.json artifacts use package-manifest composition semantics at any nested path:
tamo inspect --cwd ./my-monorepoNode project ([email protected]) in C:\my-monorepo
Artifacts:
apps/desktop/Cargo.toml (whole-file)
apps/desktop/rustfmt.toml (whole-file)
apps/web/components.json (whole-file)
apps/web/package.json (package-manifest)
apps/web/vite.config.ts (whole-file)
package.json (package-manifest)
pnpm-workspace.yaml (whole-file)
services/api/pyproject.toml (whole-file)
Workspace memberships:
apps/web/package.json <- pnpm-workspace.yamlNested setup is visible without the repository being classified: inspect never reports a project type, ecosystem, or language, and artifacts that cluster under apps/web or services/api stay plain paths. Known workspace membership links a nested manifest to the root declaration that proves it; absence of a fact means no known membership, never independence, and membership alone never triggers installation. Dependency, build, cache, and VCS trees are skipped, links are never followed, and ordinary source files are not listed.
For machine-readable output:
tamo inspect --cwd ./my-monorepo --jsonEach artifact is one { path, handler } entry (abridged here):
{
"inspection": {
"artifacts": [
{ "path": "apps/web/vite.config.ts", "handler": null },
{ "path": "apps/web/package.json", "handler": "package-manifest" },
{ "path": "package.json", "handler": "package-manifest" }
],
"workspaceMemberships": [
{ "member": "apps/web/package.json", "declaredBy": "pnpm-workspace.yaml" }
]
}
}A null handler is not missing data: it means Core's conservative whole-file fallback owns that artifact. Anything not listed can still be selected explicitly for pack with --include.
recipe
recipe reads and maintains the Recipes saved under the tamo home. list and inspect are read-only discovery; edit changes what one Recipe stores.
tamo recipe list
tamo recipe list --json
tamo recipe inspect web
tamo recipe inspect web --jsonrecipe list reports which Recipes exist, sorted by name, with a lightweight artifact count, their includes, and their Behavior entrypoint. A malformed Recipe is reported in place with its error instead of blocking the others.
recipe inspect <name> reports what one Recipe contains: includes, persistent customizations, its Behavior entrypoint, artifact paths, and the handler claiming each path. Paths are listed as stored, so nested artifacts appear as apps/web/components.json. Includes are named, not expanded.
Neither list nor inspect resolves a Recipe against a project, executes Behavior, ranks Recipes, or classifies them by ecosystem.
recipe edit
recipe edit changes the artifacts a saved Recipe stores.
tamo recipe edit web --remove apps/desktop --dry-run
tamo recipe edit web --cwd . --add biome.json --dry-run
tamo recipe edit web --cwd . --replace vite.config.ts --dry-run--add captures a path from --cwd — a file, or a directory that expands into the files beneath it — and stores it at the same relative path. Adding over a differing artifact is a conflict, not an overwrite. --replace swaps the Recipe's own stored artifact at that path; replacing something the Recipe does not store is a conflict, not an implicit add. --remove deletes one stored artifact, or every stored artifact beneath a path prefix, so removing a whole area is one command. A --remove path that matches nothing is a conflict rather than a silent success.
Capture follows the same rules as pack: secrets, keys, lockfiles, caches, dependency directories, generated output, and symlinks are never captured; a root package.json keeps package-manifest semantics (source name and version stripped, packageManager preserved and never invented) while nested manifests keep their own name and version; and the source project is never modified.
Every change is planned and validated before anything is written, so conflicting or overlapping requests leave the Recipe untouched. The result is re-loaded through the same Recipe loader recipe inspect uses.
Editing a Recipe changes reusable future setup only. Projects previously created from that Recipe are never touched, and an included Recipe is never edited through it.
Artifacts contributed by an included Recipe cannot be edited from the parent: the conflict names the owning Recipe. Includes, persistent customizations, Behavior entrypoints, and an artifact's contents are not editable here.
Safety
behavior.mjs and custom Behavior entrypoints are trusted local executable JavaScript. They run during planning, before confirmation.
Review untrusted Recipes before using them.
Other safety properties:
- reviewed inputs are fingerprinted and rechecked before execution
- changed inputs invalidate the reviewed plan
- conflicts produce zero operations instead of partial writes
- secrets, keys, lockfiles, generated output, and symlinked source state are never captured
- mutating commands support
--dry-run - external commands can still partially change state
- there is currently no automatic rollback
If execution fails after some operations have completed, tamo reports completed and remaining work so the project can be replanned before retrying.
Limitations
tamocan carry arbitrary native artifacts. Rich semantic handling exists forpackage.jsonat any nested path and for Oxlint config at the repository root. Other artifacts, includingCargo.tomlandpyproject.toml, are carried with conservative whole-file semantics.tamo packcan save selected artifacts from any directory. A rootpackage.jsonis captured when present but is not required.tamo createplans dependency installation only when the Recipe contributes a rootpackage.json. The manager comes from that manifest'spackageManagerfield (pnpm, npm, yarn, or bun; pnpm when absent), and--no-installskips it. Nested manifests and known workspace membership never trigger installs.tamo inspectdiscovers setup artifacts recursively and reports known workspace membership as facts only;packstill captures a rootpackage.jsonplus explicit--includepaths. This is not full monorepo support: per-member or filtered installs, scoped whole-subtree packing, automatic whole-repository packing, and nested package rename policies are not implemented. The single root install's workspace behavior is delegated to the package manager, and no install-coverage model is planned. Nested names are preserved, never reconciled.tamo recipe editadds, replaces, and removes a Recipe's stored artifacts. Editing an artifact's contents, includes, persistent customizations, or Behavior is not implemented.- Native integration has primarily been exercised on Windows x64.
- Behavior is trusted local code and is not sandboxed.
- There is no automatic rollback after partial execution failures.
Development
Requires Node.js 24.15+ and pnpm.
pnpm install
pnpm check
pnpm format
pnpm test:integrationTo run the CLI directly from source:
pnpm install
node src/cli.ts --helpSee SPEC.md for the full contract and PARKING_LOT.md for deferred work.
License
MIT. See LICENSE.
