@ajaykumarnpm/talea 0.3.0 → 0.4.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/CHANGELOG.md +13 -0
- package/README.md +26 -0
- package/package.json +2 -1
- package/skills/talea/SKILL.md +172 -0
- package/src/cli.js +7 -0
- package/src/commands/init.js +7 -10
- package/src/commands/skill.js +142 -0
- package/src/theme.js +16 -11
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@ All notable changes to this project are recorded here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the versions follow
|
|
5
5
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.4.0] - 2026-09-22
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- talea skill, and the CLI in the design system's palette
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **test**: skill.test.js asserted LF against a CRLF checkout
|
|
14
|
+
- **test**: init.test.js depended on the author's own GitHub auth
|
|
15
|
+
- create group folders only when cloning
|
|
16
|
+
|
|
17
|
+
### Documentation
|
|
18
|
+
- the site and the manual, at talea-run.web.app
|
|
19
|
+
|
|
7
20
|
## [0.3.0] - 2026-09-21
|
|
8
21
|
|
|
9
22
|
### Added
|
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
**One folder structure for every machine you work on.**
|
|
4
4
|
|
|
5
|
+
[talea-run.web.app](https://talea-run.web.app) — the site, and [the manual](https://talea-run.web.app/docs/).
|
|
6
|
+
|
|
5
7
|
You have a laptop, a desktop, and a work machine. On each one, the repo you want
|
|
6
8
|
is either missing or somewhere you have to go and find. `talea` fixes that: one
|
|
7
9
|
catalogue of your GitHub repos, one tree, and a command that makes any machine
|
|
@@ -64,6 +66,21 @@ talea select # reopen the checklist and change the whole list
|
|
|
64
66
|
cd $(talea where eklavya)
|
|
65
67
|
```
|
|
66
68
|
|
|
69
|
+
## Let your coding agent do it
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
talea skill install
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Installs a skill into Claude Code at **user scope**, so every project you open
|
|
76
|
+
has it. After that `"where is eklavya?"` and `"my repos are scattered, tidy them
|
|
77
|
+
up"` reach the right command — with the guardrails attached: an adopt is always
|
|
78
|
+
shown as a dry run first, `--loose` is never taken on your behalf, and a removal
|
|
79
|
+
is reported as *taken off the list* rather than as a delete.
|
|
80
|
+
|
|
81
|
+
`talea skill` says whether it is installed, `talea skill uninstall` takes it back
|
|
82
|
+
out, and it refuses to overwrite a skill called `talea` that talea did not write.
|
|
83
|
+
|
|
67
84
|
---
|
|
68
85
|
|
|
69
86
|
## The repos you already have
|
|
@@ -154,6 +171,7 @@ Treat the id like a bookmark you would not paste into a public channel.
|
|
|
154
171
|
| `talea tree` | the folder tree on disk |
|
|
155
172
|
| `talea exec -- <cmd>` | run one command in every repo |
|
|
156
173
|
| `talea manifest push/pull` | move the catalogue between machines |
|
|
174
|
+
| `talea skill` | install the skill that lets your coding agent drive talea |
|
|
157
175
|
| `talea doctor` | check this machine can do the work |
|
|
158
176
|
| `talea upgrade` | update the CLI itself |
|
|
159
177
|
|
|
@@ -195,6 +213,14 @@ node bin/talea.js --help
|
|
|
195
213
|
|
|
196
214
|
Tests run on macOS, Linux and Windows across Node 20, 22 and 24 on every push.
|
|
197
215
|
|
|
216
|
+
The website lives in `web/` and is its own thing — Astro, deployed to Firebase
|
|
217
|
+
Hosting on a push to `main` that touches it. `web/CLAUDE.md` is how to work on
|
|
218
|
+
it.
|
|
219
|
+
|
|
220
|
+
```sh
|
|
221
|
+
cd web && npm install && npm run dev
|
|
222
|
+
```
|
|
223
|
+
|
|
198
224
|
## Licence
|
|
199
225
|
|
|
200
226
|
MIT.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ajaykumarnpm/talea",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "One folder structure for every machine. Clone, adopt and sync your GitHub repos from a manifest you own.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"bin",
|
|
17
17
|
"src",
|
|
18
18
|
"manifest",
|
|
19
|
+
"skills",
|
|
19
20
|
"templates",
|
|
20
21
|
"README.md",
|
|
21
22
|
"CHANGELOG.md"
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: talea
|
|
3
|
+
description: "Operate talea, the CLI that gives every machine the same folder structure for its GitHub repos. Use when the user asks where a repo is, to clone or sync or pull their repos, to tidy or reorganise a workspace, to adopt checkouts that are in the wrong place, to change what this machine keeps, or to move their repo catalogue to another machine. Also use before running a command in several repos at once. Do not use for ordinary git work inside one repo that is already checked out."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- talea-skill: installed by `talea skill install`; edits are overwritten -->
|
|
7
|
+
|
|
8
|
+
# talea
|
|
9
|
+
|
|
10
|
+
`talea` keeps one folder structure across every machine a developer works on.
|
|
11
|
+
It holds a catalogue of their GitHub repos, moves checkouts they already have
|
|
12
|
+
into place, clones what is missing, and fast-forwards the rest.
|
|
13
|
+
|
|
14
|
+
Two rules make it safe to hand to an agent: it never pushes, and it never
|
|
15
|
+
deletes. Everything below leans on that.
|
|
16
|
+
|
|
17
|
+
## Find the binary first
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
command -v talea
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
That prints the path, or nothing. It is installed with
|
|
24
|
+
`npm install -g @ajaykumarnpm/talea` — the package is scoped, the command is
|
|
25
|
+
not. If it prints nothing, fall back to `npx -y @ajaykumarnpm/talea <command>`,
|
|
26
|
+
which downloads on first use. If that fails too, say talea is not installed and
|
|
27
|
+
give the one install command rather than guessing at a path.
|
|
28
|
+
|
|
29
|
+
Shell variables do not survive between tool calls, so write the resolved path
|
|
30
|
+
into every later command rather than setting `T=` and hoping.
|
|
31
|
+
|
|
32
|
+
Everything below writes `talea` for readability. Substitute whatever the line
|
|
33
|
+
above resolved to.
|
|
34
|
+
|
|
35
|
+
## Where is this repo?
|
|
36
|
+
|
|
37
|
+
This is the command to reach for most, and the reason the tool exists.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
talea where eklavya # the absolute path, one line, nothing else
|
|
41
|
+
cd "$(talea where eklavya)" # what it is actually for
|
|
42
|
+
talea where # the workspace root
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
It exits **non-zero** on an unknown name and writes every diagnostic to stderr,
|
|
46
|
+
so `cd "$(talea where typo)"` fails instead of landing in the home directory.
|
|
47
|
+
Never `cd` to a path it did not print.
|
|
48
|
+
|
|
49
|
+
Two owners can own a repo of the same name. When that happens it says so and
|
|
50
|
+
exits non-zero — pass `owner/name` rather than picking one.
|
|
51
|
+
|
|
52
|
+
## Read before you write
|
|
53
|
+
|
|
54
|
+
None of these change anything on disk. Run one before proposing work.
|
|
55
|
+
|
|
56
|
+
| Command | Answers |
|
|
57
|
+
|---|---|
|
|
58
|
+
| `talea status` | branch, clean or dirty, ahead or behind, for every repo this machine keeps |
|
|
59
|
+
| `talea list` | the catalogue — every repo, its group, its folder |
|
|
60
|
+
| `talea tree` | the folder tree as it actually is on disk |
|
|
61
|
+
| `talea doctor` | can this machine do the work: git, SSH, GitHub auth, the workspace |
|
|
62
|
+
| `talea adopt` | what a move *would* do. It changes nothing without `--apply` |
|
|
63
|
+
|
|
64
|
+
`talea status` is the one to run before suggesting a sync — a dirty repo is
|
|
65
|
+
skipped, and saying so up front is better than reporting it afterwards.
|
|
66
|
+
|
|
67
|
+
## Bringing a machine into line
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
talea sync # clone what is missing, fast-forward what is there
|
|
71
|
+
talea clone # clone only — never fetches or merges
|
|
72
|
+
talea sync -g NonStop # one group
|
|
73
|
+
talea sync -r eklavya # one repo
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`sync` is safe to run unattended. It fast-forwards **the branch you are on** and
|
|
77
|
+
leaves anything else alone with a line saying why. It never switches branches,
|
|
78
|
+
never merges a divergence and never pushes.
|
|
79
|
+
|
|
80
|
+
Every bulk command exits non-zero if **any** repo failed, so check the exit
|
|
81
|
+
code — the per-repo errors are printed but the run keeps going.
|
|
82
|
+
|
|
83
|
+
## Adoption: the one that moves things
|
|
84
|
+
|
|
85
|
+
A repo the developer already has, in the wrong folder, is **moved** into place
|
|
86
|
+
rather than re-cloned. A move keeps branches, stashes, the reflog and
|
|
87
|
+
uncommitted work; a re-clone throws all of it away.
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
talea adopt # show what would move, change nothing
|
|
91
|
+
talea adopt --from ~/Desktop # look there too; the folder is remembered
|
|
92
|
+
talea adopt --apply # do it
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Always run it without `--apply` first and show the developer the plan.** A
|
|
96
|
+
move relocates directories they may have open in an editor, a terminal or a
|
|
97
|
+
long-running process.
|
|
98
|
+
|
|
99
|
+
Matching is on the **git remote URL**, not the folder name. When only the name
|
|
100
|
+
matches — a fork, a mirror, an SDK cache — it is listed and left alone. `--loose`
|
|
101
|
+
or `-r <repo>` includes it. Do not reach for `--loose` on the developer's behalf:
|
|
102
|
+
the guard exists because an FVM Flutter SDK cache reports `origin` as
|
|
103
|
+
`flutter/flutter` and name-matches a personal fork, and adopting it breaks every
|
|
104
|
+
Flutter project on the machine.
|
|
105
|
+
|
|
106
|
+
A second copy of the same repo is **parked** in `.talea-duplicates/`, not
|
|
107
|
+
deleted. Say so when it happens — clearing that folder is the developer's call,
|
|
108
|
+
and nothing in talea will do it for them.
|
|
109
|
+
|
|
110
|
+
## What this machine keeps
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
talea add some-repo # keep one more here, and clone it now
|
|
114
|
+
talea rm some-repo # stop keeping it — the checkout stays exactly where it is
|
|
115
|
+
talea select # reopen the whole checklist, interactively
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`talea select` needs a terminal. In a non-interactive session it prints a
|
|
119
|
+
summary instead of hanging, so prefer `add` and `rm` when acting on the
|
|
120
|
+
developer's behalf and leave `select` as something to suggest they run.
|
|
121
|
+
|
|
122
|
+
`rm` does **not** delete the checkout. Say that plainly when you run it, or it
|
|
123
|
+
reads like data loss.
|
|
124
|
+
|
|
125
|
+
## Running one command everywhere
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
talea exec -- git status --short
|
|
129
|
+
talea exec -g NonStop -- npm test
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Everything after `--` runs in each repo. Treat it as you would any command run
|
|
133
|
+
in N repositories at once: read-only commands freely, anything that writes only
|
|
134
|
+
when the developer asked for it by name.
|
|
135
|
+
|
|
136
|
+
## Moving the catalogue between machines
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
talea manifest push # publish it; prints a gist id
|
|
140
|
+
talea manifest pull <gist-id> # on the next machine
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The gist is **private**, but it still carries the names of private
|
|
144
|
+
repositories. Treat the id as something not to paste into a public channel, and
|
|
145
|
+
do not print it into a message that is going somewhere shared.
|
|
146
|
+
|
|
147
|
+
The split that matters: the **catalogue** travels, the **selection** does not.
|
|
148
|
+
Pulling it onto a new laptop hands over the full list to choose from, never the
|
|
149
|
+
last machine's choices. So `manifest pull` alone changes nothing about what is
|
|
150
|
+
cloned — `talea init` or `talea select` after it is what fills the tree.
|
|
151
|
+
|
|
152
|
+
## Narrowing any run
|
|
153
|
+
|
|
154
|
+
Every command takes `-g <group>` and `-r <repo>`, both repeatable, and `--help`
|
|
155
|
+
for its own examples. An unknown group or repo name **exits non-zero** rather
|
|
156
|
+
than quietly doing nothing, so a typo is loud.
|
|
157
|
+
|
|
158
|
+
## Rules
|
|
159
|
+
|
|
160
|
+
- **Never `--apply` an adopt the developer has not seen the dry run of.**
|
|
161
|
+
- **Never `--loose`** unless they have looked at the name-only matches and said
|
|
162
|
+
which one they want.
|
|
163
|
+
- Report a move as *moved*, a removal as *taken off the list*, and a duplicate
|
|
164
|
+
as *parked*. Those are three different things and the words are the whole
|
|
165
|
+
safety story.
|
|
166
|
+
- Do not add `ignore: true` to a catalogue entry to work around a conflict
|
|
167
|
+
without saying so — it means another tool owns that checkout, and every talea
|
|
168
|
+
command will skip it from then on.
|
|
169
|
+
- talea never pushes. If a repo is ahead of its remote, report it and stop;
|
|
170
|
+
pushing is the developer's call and not talea's job.
|
|
171
|
+
- A bulk command that exits non-zero has a failed repo in it. Read the output
|
|
172
|
+
for the failures rather than reporting the run as done.
|
package/src/cli.js
CHANGED
|
@@ -20,6 +20,7 @@ import * as manifest from './commands/manifest.js';
|
|
|
20
20
|
import * as doctor from './commands/doctor.js';
|
|
21
21
|
import * as exec from './commands/exec.js';
|
|
22
22
|
import * as upgrade from './commands/upgrade.js';
|
|
23
|
+
import * as skill from './commands/skill.js';
|
|
23
24
|
import { notifyIfOutdatedAsync } from './update.js';
|
|
24
25
|
|
|
25
26
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -41,6 +42,7 @@ const COMMANDS = {
|
|
|
41
42
|
manifest,
|
|
42
43
|
doctor,
|
|
43
44
|
exec,
|
|
45
|
+
skill,
|
|
44
46
|
upgrade,
|
|
45
47
|
};
|
|
46
48
|
|
|
@@ -59,6 +61,7 @@ const ALIASES = {
|
|
|
59
61
|
cd: 'where',
|
|
60
62
|
path: 'where',
|
|
61
63
|
run: 'exec',
|
|
64
|
+
skills: 'skill',
|
|
62
65
|
remove: 'rm',
|
|
63
66
|
'self-update': 'upgrade',
|
|
64
67
|
};
|
|
@@ -115,6 +118,7 @@ ${c.bold('Commands')}
|
|
|
115
118
|
${c.cyan('exec')} run one command in every repo
|
|
116
119
|
${c.cyan('manifest')} push or pull the catalogue through a private gist
|
|
117
120
|
${c.cyan('doctor')} check git, SSH, GitHub auth and workspace health
|
|
121
|
+
${c.cyan('skill')} install the skill that lets your coding agent drive talea
|
|
118
122
|
${c.cyan('upgrade')} update the CLI itself
|
|
119
123
|
|
|
120
124
|
${c.bold('A new machine')}
|
|
@@ -130,6 +134,9 @@ ${c.bold('Every day')}
|
|
|
130
134
|
talea sync ${c.dim('# clone the new, fast-forward the rest')}
|
|
131
135
|
talea status --drift ${c.dim('# what is not where I left it')}
|
|
132
136
|
|
|
137
|
+
${c.bold('With an agent')}
|
|
138
|
+
talea skill install ${c.dim('# then ask Claude Code "where is eklavya?"')}
|
|
139
|
+
|
|
133
140
|
${c.bold('Common options')}
|
|
134
141
|
-g, --group <names> restrict to groups, e.g. -g nonstopio
|
|
135
142
|
-r, --repo <names> restrict to repos, e.g. -r eklavya
|
package/src/commands/init.js
CHANGED
|
@@ -7,14 +7,11 @@ import {
|
|
|
7
7
|
STATE_FILE,
|
|
8
8
|
expandHome,
|
|
9
9
|
findWorkspace,
|
|
10
|
-
groupDir,
|
|
11
10
|
loadManifest,
|
|
12
|
-
loadState,
|
|
13
11
|
repoGroup,
|
|
14
12
|
saveState,
|
|
15
13
|
} from '../config.js';
|
|
16
14
|
import { samePath } from '../adopt.js';
|
|
17
|
-
import { machineRepos } from '../workspace.js';
|
|
18
15
|
import { c, context, fail, heading, info, ok, plain, skip, warn } from '../log.js';
|
|
19
16
|
import { run as discover } from './discover.js';
|
|
20
17
|
import { run as sync } from './sync.js';
|
|
@@ -129,13 +126,13 @@ export async function run(opts, positionals = []) {
|
|
|
129
126
|
['groups', `${groups.length}`],
|
|
130
127
|
]);
|
|
131
128
|
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
129
|
+
// No group folders are created here. `init` runs before the picker — the
|
|
130
|
+
// checklist lives in `sync` — so anything made at this point is made from the
|
|
131
|
+
// catalogue's defaults, which is every owner the discovery found. Picking
|
|
132
|
+
// four repos out of 228 then left 18 empty owner folders in the tree, and an
|
|
133
|
+
// empty folder is indistinguishable from a checkout somebody deleted.
|
|
134
|
+
// `cloneMissing` creates the parent of each repo it is about to clone, so the
|
|
135
|
+
// only folders that ever appear are the ones with something in them.
|
|
139
136
|
|
|
140
137
|
if (opts.clone === false) {
|
|
141
138
|
plain(`\n${c.dim('Workspace ready. Run `talea sync` when you want the repos.')}`);
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// `talea skill` — put the agent-facing skill in the user's Claude Code.
|
|
2
|
+
//
|
|
3
|
+
// The skill is a single Markdown file describing how to drive this CLI safely:
|
|
4
|
+
// which commands read, which move things, and which two flags must never be
|
|
5
|
+
// reached for on somebody's behalf. Installing it at **user scope**
|
|
6
|
+
// (`~/.claude/skills/`) rather than in a repo is the point — the developer has
|
|
7
|
+
// one workspace across every project, so the agent needs the same instructions
|
|
8
|
+
// in all of them.
|
|
9
|
+
|
|
10
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs';
|
|
11
|
+
import os from 'node:os';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
|
|
15
|
+
import { c, fail, heading, info, ok, plain, warn } from '../log.js';
|
|
16
|
+
|
|
17
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
|
|
19
|
+
/** The copy that ships in the package. */
|
|
20
|
+
export const source = path.join(here, '..', '..', 'skills', 'talea', 'SKILL.md');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Claude Code reads user-scope skills from `~/.claude/skills/<name>/SKILL.md`.
|
|
24
|
+
* `CLAUDE_CONFIG_DIR` is how somebody moves that directory, and honouring it is
|
|
25
|
+
* the difference between installing the skill and installing it somewhere
|
|
26
|
+
* nothing will ever read it.
|
|
27
|
+
*/
|
|
28
|
+
export const target = () =>
|
|
29
|
+
path.join(
|
|
30
|
+
process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude'),
|
|
31
|
+
'skills',
|
|
32
|
+
'talea',
|
|
33
|
+
'SKILL.md',
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The marker that says a file at that path is ours.
|
|
38
|
+
*
|
|
39
|
+
* Somebody may already have their own skill called `talea`. Overwriting it
|
|
40
|
+
* would destroy work this tool has no claim on, so the only file `install`
|
|
41
|
+
* will replace is one carrying this line, and the only file `uninstall` will
|
|
42
|
+
* delete is the same.
|
|
43
|
+
*/
|
|
44
|
+
const MARK = '<!-- talea-skill:';
|
|
45
|
+
|
|
46
|
+
export const isOurs = (file) => {
|
|
47
|
+
try {
|
|
48
|
+
return readFileSync(file, 'utf8').includes(MARK);
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const help = `
|
|
55
|
+
${c.bold('talea skill')} — teach your coding agent to drive talea
|
|
56
|
+
|
|
57
|
+
${c.dim('talea skill')} is it installed, and where
|
|
58
|
+
${c.dim('talea skill install')} copy it into ~/.claude/skills/talea/
|
|
59
|
+
${c.dim('talea skill uninstall')} take it back out
|
|
60
|
+
|
|
61
|
+
Installs at ${c.bold('user scope')}, so every project you open gets it — your workspace
|
|
62
|
+
spans all of them, and the agent needs the same instructions in each.
|
|
63
|
+
|
|
64
|
+
The skill is what turns ${c.dim('"where is eklavya?"')} and ${c.dim('"tidy up my repos"')} into the
|
|
65
|
+
right talea command, with the guardrails attached: an adopt is always dry-run
|
|
66
|
+
first, ${c.dim('--loose')} is never taken on your behalf, and nothing is ever deleted.
|
|
67
|
+
|
|
68
|
+
It refuses to overwrite a skill called ${c.dim('talea')} that this tool did not write.
|
|
69
|
+
Move yours first if you have one.
|
|
70
|
+
`;
|
|
71
|
+
|
|
72
|
+
export function run(opts, positionals = []) {
|
|
73
|
+
const action = positionals[0] ?? 'status';
|
|
74
|
+
const dest = target();
|
|
75
|
+
|
|
76
|
+
if (action === 'status') return status(dest);
|
|
77
|
+
if (action === 'install') return install(dest);
|
|
78
|
+
if (action === 'uninstall' || action === 'remove') return uninstall(dest);
|
|
79
|
+
|
|
80
|
+
fail(`Unknown: talea skill ${action}`);
|
|
81
|
+
plain(`\n Try ${c.dim('talea skill install')}, ${c.dim('talea skill uninstall')}, or ${c.dim('talea skill')} on its own.`);
|
|
82
|
+
// `process.exitCode`, not `process.exit()` — the same contract `summary()`
|
|
83
|
+
// uses, and the only one a test can drive without taking the runner down.
|
|
84
|
+
process.exitCode = 1;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function status(dest) {
|
|
88
|
+
heading('talea skill');
|
|
89
|
+
if (!existsSync(dest)) {
|
|
90
|
+
warn('Not installed.');
|
|
91
|
+
plain(`\n ${c.dim('talea skill install')} put it in ${c.bold(dest)}`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (!isOurs(dest)) {
|
|
95
|
+
warn(`Something else owns ${c.bold(dest)}.`);
|
|
96
|
+
plain(`\n It is not a file talea wrote, so talea will not touch it.`);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const same = readFileSync(dest, 'utf8') === readFileSync(source, 'utf8');
|
|
100
|
+
ok(`Installed at ${c.bold(dest)}`);
|
|
101
|
+
if (!same) {
|
|
102
|
+
plain(`\n ${c.yellow('Out of date')} — ${c.dim('talea skill install')} rewrites it from this version.`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function install(dest) {
|
|
107
|
+
heading('talea skill install');
|
|
108
|
+
|
|
109
|
+
if (existsSync(dest) && !isOurs(dest)) {
|
|
110
|
+
// Somebody's own skill, under the name we want. Never overwrite it: it is
|
|
111
|
+
// work this tool has no claim on, and there is no undo.
|
|
112
|
+
fail(`There is already a skill called "talea" at ${c.bold(dest)}, and talea did not write it.`);
|
|
113
|
+
plain(`\n Move it somewhere else first, then run this again. Nothing has been changed.`);
|
|
114
|
+
process.exitCode = 1;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
mkdirSync(path.dirname(dest), { recursive: true });
|
|
119
|
+
copyFileSync(source, dest);
|
|
120
|
+
ok(`Installed ${c.bold(dest)}`);
|
|
121
|
+
info(`Restart Claude Code to pick it up — skills are read at session start.`);
|
|
122
|
+
plain(`\n Then ${c.dim('"where is eklavya?"')} or ${c.dim('"sync my repos"')} reaches the right command on its own.`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function uninstall(dest) {
|
|
126
|
+
heading('talea skill uninstall');
|
|
127
|
+
|
|
128
|
+
if (!existsSync(dest)) {
|
|
129
|
+
info('Nothing to remove.');
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (!isOurs(dest)) {
|
|
133
|
+
fail(`${c.bold(dest)} is not a file talea wrote — leaving it alone.`);
|
|
134
|
+
process.exitCode = 1;
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
rmSync(dest, { force: true });
|
|
138
|
+
// The directory is ours too, and an empty one left behind shows up in
|
|
139
|
+
// `/skills` as a skill with no file.
|
|
140
|
+
rmSync(path.dirname(dest), { recursive: true, force: true });
|
|
141
|
+
ok(`Removed ${c.bold(dest)}`);
|
|
142
|
+
}
|
package/src/theme.js
CHANGED
|
@@ -24,18 +24,23 @@ export const useColor =
|
|
|
24
24
|
// fallback and uses it whenever COLORTERM is not set.
|
|
25
25
|
const trueColor = /^(truecolor|24bit)$/i.test(process.env.COLORTERM ?? '');
|
|
26
26
|
|
|
27
|
-
// ──
|
|
28
|
-
// [24-bit hex, basic-16 SGR code]. The
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
27
|
+
// ── Verdigris palette ──────────────────────────────────────────
|
|
28
|
+
// [24-bit hex, basic-16 SGR code]. The same accent ramp the website is built
|
|
29
|
+
// on — verdigris, new growth on aged bronze — so the CLI and talea.run are one
|
|
30
|
+
// product rather than two that share a name. The hexes are the `--vd-*` steps in
|
|
31
|
+
// `web/public/tokens.css`; change one and change the other.
|
|
32
|
+
//
|
|
33
|
+
// The basic code is what a terminal without COLORTERM gets. It is deliberately
|
|
34
|
+
// the closest *readable* match rather than the closest numerically — `faint`
|
|
35
|
+
// has no bright equivalent, so it falls back to grey rather than a teal nobody
|
|
36
|
+
// can read on a light background.
|
|
32
37
|
const PALETTE = {
|
|
33
|
-
green: ['#
|
|
34
|
-
aged: ['#
|
|
35
|
-
faint: ['#
|
|
36
|
-
amber: ['#
|
|
37
|
-
red: ['#
|
|
38
|
-
bone: ['#
|
|
38
|
+
green: ['#79D5C4', 96], // --vd-300, the accent — something worked
|
|
39
|
+
aged: ['#199688', 36], // --vd-500 — a branch, a path, a detail
|
|
40
|
+
faint: ['#07403C', 90], // --vd-800 — rules and fills
|
|
41
|
+
amber: ['#E8920C', 33], // --warning: attention, but not a failure
|
|
42
|
+
red: ['#E5484D', 31], // --error: failure
|
|
43
|
+
bone: ['#EAE7E1', 37], // --ink: plain text that still wants to be lit
|
|
39
44
|
};
|
|
40
45
|
|
|
41
46
|
|