@ajaykumarnpm/talea 0.5.0 → 0.7.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 +10 -0
- package/README.md +6 -3
- package/package.json +1 -1
- package/skills/talea/SKILL.md +12 -3
- package/src/cli.js +28 -4
- package/src/commands/add.js +8 -3
- package/src/commands/clone.js +1 -1
- package/src/commands/list.js +1 -1
- package/src/commands/select.js +20 -3
- package/src/commands/status.js +1 -1
- package/src/commands/sync.js +2 -1
- package/src/commands/tree.js +1 -1
- package/src/commands/upgrade.js +1 -0
- package/src/git.js +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@ 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.7.0] - 2026-09-22
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- a bare repo name narrows sync, clone, status, list and tree
|
|
11
|
+
|
|
12
|
+
## [0.6.0] - 2026-09-22
|
|
13
|
+
|
|
14
|
+
### Breaking
|
|
15
|
+
- `talea pick <repo>` keeps one repo; `talea update` upgrades the CLI
|
|
16
|
+
|
|
7
17
|
## [0.5.0] - 2026-09-22
|
|
8
18
|
|
|
9
19
|
### Added
|
package/README.md
CHANGED
|
@@ -66,6 +66,7 @@ talea sync # clone the new, fast-forward the rest
|
|
|
66
66
|
talea status # branch, clean/dirty, ahead/behind, in one table
|
|
67
67
|
talea add some-repo # keep one more on this machine, and clone it now
|
|
68
68
|
talea select # reopen the checklist and change the whole list
|
|
69
|
+
talea pick some-repo # keep that one — an unknown name opens the checklist
|
|
69
70
|
cd $(talea where eklavya)
|
|
70
71
|
```
|
|
71
72
|
|
|
@@ -167,7 +168,7 @@ Treat the id like a bookmark you would not paste into a public channel.
|
|
|
167
168
|
| `talea clone` | clone only — never fetches or merges |
|
|
168
169
|
| `talea adopt` | move checkouts you already have into place |
|
|
169
170
|
| `talea status` | branch, clean/dirty, ahead/behind |
|
|
170
|
-
| `talea select` | reopen the checklist —
|
|
171
|
+
| `talea select` | reopen the checklist — `talea pick <repo>` for one |
|
|
171
172
|
| `talea add` / `talea rm` | change that one repo at a time |
|
|
172
173
|
| `talea where <repo>` | print a repo's path, for `cd $( )` |
|
|
173
174
|
| `talea list` | the catalogue |
|
|
@@ -176,10 +177,12 @@ Treat the id like a bookmark you would not paste into a public channel.
|
|
|
176
177
|
| `talea manifest push/pull` | move the catalogue between machines |
|
|
177
178
|
| `talea skill` | install the skill that lets your coding agent drive talea |
|
|
178
179
|
| `talea doctor` | check this machine can do the work |
|
|
179
|
-
| `talea upgrade` | update the CLI itself |
|
|
180
|
+
| `talea upgrade` | update the CLI itself (also `talea update`) |
|
|
180
181
|
|
|
181
182
|
Every one of them takes `-g <group>` and `-r <repo>` to narrow the run, and
|
|
182
|
-
`--help` for its own examples.
|
|
183
|
+
`--help` for its own examples. For `sync`, `clone`, `status`, `list` and `tree`
|
|
184
|
+
a bare name means the same as `-r`, so `talea sync eklavya` syncs that one repo.
|
|
185
|
+
A command that takes no names refuses a stray word instead of ignoring it.
|
|
183
186
|
|
|
184
187
|
---
|
|
185
188
|
|
package/package.json
CHANGED
package/skills/talea/SKILL.md
CHANGED
|
@@ -74,7 +74,7 @@ skipped, and saying so up front is better than reporting it afterwards.
|
|
|
74
74
|
talea sync # clone what is missing, fast-forward what is there
|
|
75
75
|
talea clone # clone only — never fetches or merges
|
|
76
76
|
talea sync -g NonStop # one group
|
|
77
|
-
talea sync
|
|
77
|
+
talea sync eklavya # one repo — same as -r eklavya
|
|
78
78
|
```
|
|
79
79
|
|
|
80
80
|
`sync` is safe to run unattended. It fast-forwards **the branch you are on** and
|
|
@@ -117,11 +117,17 @@ and nothing in talea will do it for them.
|
|
|
117
117
|
talea add some-repo # keep one more here, and clone it now
|
|
118
118
|
talea rm some-repo # stop keeping it — the checkout stays exactly where it is
|
|
119
119
|
talea select # reopen the whole checklist, interactively
|
|
120
|
+
talea pick some-repo # = add when the name is exact; otherwise the checklist
|
|
120
121
|
```
|
|
121
122
|
|
|
122
123
|
`talea select` needs a terminal. In a non-interactive session it prints a
|
|
123
124
|
summary instead of hanging, so prefer `add` and `rm` when acting on the
|
|
124
125
|
developer's behalf and leave `select` as something to suggest they run.
|
|
126
|
+
`pick <name>` falls back to that same checklist on a typo, so an agent uses
|
|
127
|
+
`add`, which fails loudly instead.
|
|
128
|
+
|
|
129
|
+
`talea update` reinstalls the CLI (it is `upgrade`), not `sync`. Never run it
|
|
130
|
+
to refresh repos.
|
|
125
131
|
|
|
126
132
|
`rm` does **not** delete the checkout. Say that plainly when you run it, or it
|
|
127
133
|
reads like data loss.
|
|
@@ -156,8 +162,11 @@ cloned — `talea init` or `talea select` after it is what fills the tree.
|
|
|
156
162
|
## Narrowing any run
|
|
157
163
|
|
|
158
164
|
Every command takes `-g <group>` and `-r <repo>`, both repeatable, and `--help`
|
|
159
|
-
for its own examples.
|
|
160
|
-
|
|
165
|
+
for its own examples. For `sync`, `clone`, `status`, `list` and `tree` a bare
|
|
166
|
+
name is the same as `-r`. `adopt` is the exception: it refuses a bare name,
|
|
167
|
+
because `-r` there lifts the name-only guard and must be typed on purpose. An
|
|
168
|
+
unknown group or repo name **exits non-zero** rather than quietly doing nothing,
|
|
169
|
+
so a typo is loud.
|
|
161
170
|
|
|
162
171
|
## Rules
|
|
163
172
|
|
package/src/cli.js
CHANGED
|
@@ -52,7 +52,6 @@ const ALIASES = {
|
|
|
52
52
|
setup: 'init',
|
|
53
53
|
bootstrap: 'init',
|
|
54
54
|
pull: 'sync',
|
|
55
|
-
update: 'sync',
|
|
56
55
|
refresh: 'discover',
|
|
57
56
|
pick: 'select',
|
|
58
57
|
choose: 'select',
|
|
@@ -63,9 +62,28 @@ const ALIASES = {
|
|
|
63
62
|
run: 'exec',
|
|
64
63
|
skills: 'skill',
|
|
65
64
|
remove: 'rm',
|
|
65
|
+
update: 'upgrade',
|
|
66
66
|
'self-update': 'upgrade',
|
|
67
67
|
};
|
|
68
68
|
|
|
69
|
+
// Bare words after these commands are repo names: `talea sync PiDom` is
|
|
70
|
+
// `talea sync -r PiDom`. Dropping them instead ran the command over every repo
|
|
71
|
+
// on the machine, which is a typo guard failing in the worst direction.
|
|
72
|
+
const TAKES_REPOS = new Set(['sync', 'clone', 'status', 'list', 'tree']);
|
|
73
|
+
|
|
74
|
+
// These read their own positionals. Everything else takes none, and a stray
|
|
75
|
+
// word stops the run. `adopt` is left out on purpose: an explicit -r there lifts
|
|
76
|
+
// the name-only guard, and a bare word must not do that by accident.
|
|
77
|
+
const TAKES_WORDS = new Set(['init', 'select', 'add', 'rm', 'manifest', 'exec', 'skill', 'where']);
|
|
78
|
+
|
|
79
|
+
/** Fold a command's bare words into -r, pass them through, or refuse them. */
|
|
80
|
+
export function routeWords(key, words, repo) {
|
|
81
|
+
if (!words.length || TAKES_WORDS.has(key)) return { repo, words };
|
|
82
|
+
if (TAKES_REPOS.has(key)) return { repo: [...(repo ?? []), ...words], words: [] };
|
|
83
|
+
const hint = key === 'adopt' ? `\n To name a repo: talea adopt -r ${words.join(',')}` : '';
|
|
84
|
+
return { error: `\`${key}\` takes no arguments, got ${words.map((w) => `"${w}"`).join(' ')}.${hint}` };
|
|
85
|
+
}
|
|
86
|
+
|
|
69
87
|
const OPTIONS = {
|
|
70
88
|
group: { type: 'string', short: 'g', multiple: true },
|
|
71
89
|
repo: { type: 'string', short: 'r', multiple: true },
|
|
@@ -110,7 +128,7 @@ ${c.bold('Commands')}
|
|
|
110
128
|
${c.cyan('clone')} clone only — never fetches or merges
|
|
111
129
|
${c.cyan('adopt')} move repos you already have into the right place
|
|
112
130
|
${c.cyan('status')} one table: branch, clean/dirty, ahead/behind
|
|
113
|
-
${c.cyan('select')} reopen the checklist —
|
|
131
|
+
${c.cyan('select')} reopen the checklist — or ${c.dim('talea pick <repo>')} for one
|
|
114
132
|
${c.cyan('add')} keep another repo on this machine (${c.dim('rm')} to drop one)
|
|
115
133
|
${c.cyan('where')} print a repo's path — ${c.dim('cd $(talea where eklavya)')}
|
|
116
134
|
${c.cyan('list')} show the catalogue
|
|
@@ -203,6 +221,12 @@ export async function main(argv) {
|
|
|
203
221
|
}
|
|
204
222
|
}
|
|
205
223
|
|
|
224
|
+
const routed = routeWords(key, rest, values.repo?.length ? values.repo : undefined);
|
|
225
|
+
if (routed.error) {
|
|
226
|
+
fail(routed.error);
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
|
|
206
230
|
const opts = {
|
|
207
231
|
...values,
|
|
208
232
|
jobs,
|
|
@@ -216,10 +240,10 @@ export async function main(argv) {
|
|
|
216
240
|
// selectRepos to flatten, but normalise "not passed" to undefined.
|
|
217
241
|
group: values.group?.length ? values.group : undefined,
|
|
218
242
|
from: values.from?.length ? values.from : undefined,
|
|
219
|
-
repo:
|
|
243
|
+
repo: routed.repo,
|
|
220
244
|
};
|
|
221
245
|
|
|
222
|
-
await command.run(opts, key === 'exec' ? tail :
|
|
246
|
+
await command.run(opts, key === 'exec' ? tail : routed.words);
|
|
223
247
|
|
|
224
248
|
// After the real work, never before it, and never able to fail it.
|
|
225
249
|
if (key !== 'upgrade') {
|
package/src/commands/add.js
CHANGED
|
@@ -34,12 +34,17 @@ Options
|
|
|
34
34
|
-j, --jobs <n> parallel clones
|
|
35
35
|
`;
|
|
36
36
|
|
|
37
|
-
/**
|
|
38
|
-
export function
|
|
37
|
+
/** Every catalogue entry `name` or `owner/name` could mean, ignoring case. */
|
|
38
|
+
export function lookup(manifest, name) {
|
|
39
39
|
const wanted = String(name).toLowerCase();
|
|
40
|
-
|
|
40
|
+
return manifest.repos.filter(
|
|
41
41
|
(r) => r.name.toLowerCase() === wanted || `${r.owner}/${r.name}`.toLowerCase() === wanted,
|
|
42
42
|
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Resolve `name` or `owner/name` against the catalogue, or exit saying why. */
|
|
46
|
+
export function resolve(manifest, name) {
|
|
47
|
+
const matches = lookup(manifest, name);
|
|
43
48
|
|
|
44
49
|
if (!matches.length) {
|
|
45
50
|
fail(`No repo called "${name}" in the catalogue.`);
|
package/src/commands/clone.js
CHANGED
|
@@ -20,7 +20,7 @@ ${c.bold('talea clone')} — clone what is missing, and nothing else
|
|
|
20
20
|
|
|
21
21
|
Options
|
|
22
22
|
-g, --group <names> comma-separated groups
|
|
23
|
-
-r, --repo <names> comma-separated repo names
|
|
23
|
+
-r, --repo <names> comma-separated repo names; a bare name works too
|
|
24
24
|
--pick choose what this machine keeps before cloning
|
|
25
25
|
--protocol <p> ssh (default) or https
|
|
26
26
|
--from <path> also search here for existing checkouts (repeatable)
|
package/src/commands/list.js
CHANGED
|
@@ -15,7 +15,7 @@ the catalogue's opinion — what a brand new machine would start with.
|
|
|
15
15
|
|
|
16
16
|
Options
|
|
17
17
|
-g, --group <names> comma-separated groups
|
|
18
|
-
-r, --repo <names> comma-separated repo names
|
|
18
|
+
-r, --repo <names> comma-separated repo names; a bare name works too
|
|
19
19
|
--all include archived and quiet repos (default: hidden)
|
|
20
20
|
--groups show groups only
|
|
21
21
|
--json emit JSON
|
package/src/commands/select.js
CHANGED
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
// because `clone` and `sync` ask the same question, and the cloning is
|
|
10
10
|
// `clone.run`, re-entered after the new selection is on disk.
|
|
11
11
|
|
|
12
|
-
import { c, fail, heading, ok, plain, skip } from '../log.js';
|
|
12
|
+
import { c, fail, heading, ok, plain, skip, warn } from '../log.js';
|
|
13
13
|
import { chooseRepos } from '../select.js';
|
|
14
14
|
import { machineRepos, requireCatalogue, requireWorkspace } from '../workspace.js';
|
|
15
|
+
import * as add from './add.js';
|
|
15
16
|
import * as clone from './clone.js';
|
|
16
17
|
|
|
17
18
|
export const help = `
|
|
@@ -19,6 +20,8 @@ ${c.bold('talea select')} — change what this machine keeps
|
|
|
19
20
|
|
|
20
21
|
${c.dim('talea select')} reopen the checklist, then clone what is newly ticked
|
|
21
22
|
${c.dim('talea select --no-clone')} change the list only, fill it in later
|
|
23
|
+
${c.dim('talea pick PiDom')} keep that one repo and clone it — or, if no repo
|
|
24
|
+
has that name, open the checklist instead
|
|
22
25
|
|
|
23
26
|
The checklist opens with the current selection ticked. Space toggles, Enter
|
|
24
27
|
saves, Esc cancels and changes nothing. Everything in the catalogue is listed,
|
|
@@ -29,7 +32,10 @@ It writes ${c.dim('.talea.json')}, which never leaves this machine, so no other
|
|
|
29
32
|
selection changes. Unticking a repo takes it off the list and leaves the
|
|
30
33
|
checkout exactly where it is; deleting it is your call.
|
|
31
34
|
|
|
32
|
-
|
|
35
|
+
A name that matches exactly one repo (case does not matter, ${c.dim('owner/name')} works)
|
|
36
|
+
is ${c.dim('talea add <repo>')}. A name that matches none, or two owners' repos, opens the
|
|
37
|
+
checklist, because a typo should land you in the list rather than nowhere.
|
|
38
|
+
${c.dim('talea rm <repo>')} takes one off.
|
|
33
39
|
|
|
34
40
|
Options
|
|
35
41
|
--no-clone save the selection, clone nothing
|
|
@@ -37,6 +43,11 @@ Options
|
|
|
37
43
|
-j, --jobs <n> parallel clones
|
|
38
44
|
`;
|
|
39
45
|
|
|
46
|
+
/** The names that do not pin down exactly one repo — empty means every one does. */
|
|
47
|
+
export function unresolved(manifest, names) {
|
|
48
|
+
return names.filter((n) => add.lookup(manifest, n).length !== 1);
|
|
49
|
+
}
|
|
50
|
+
|
|
40
51
|
/** What changed, as two lists of names. Pure, so it can be tested. */
|
|
41
52
|
export function changes(before, after) {
|
|
42
53
|
const had = new Set(before);
|
|
@@ -47,7 +58,7 @@ export function changes(before, after) {
|
|
|
47
58
|
};
|
|
48
59
|
}
|
|
49
60
|
|
|
50
|
-
export async function run(opts) {
|
|
61
|
+
export async function run(opts, positionals = []) {
|
|
51
62
|
const { root, manifest, state } = await requireWorkspace();
|
|
52
63
|
requireCatalogue(manifest);
|
|
53
64
|
|
|
@@ -60,6 +71,12 @@ export async function run(opts) {
|
|
|
60
71
|
process.exit(1);
|
|
61
72
|
}
|
|
62
73
|
|
|
74
|
+
if (positionals.length) {
|
|
75
|
+
const missing = unresolved(manifest, positionals);
|
|
76
|
+
if (!missing.length) return add.run({ ...opts, removing: false }, positionals);
|
|
77
|
+
warn(`No single repo called ${missing.map((n) => `"${n}"`).join(', ')} — opening the checklist.`);
|
|
78
|
+
}
|
|
79
|
+
|
|
63
80
|
const before = machineRepos(manifest, state).map((r) => r.name);
|
|
64
81
|
const { repos } = await chooseRepos({ manifest, root, state, opts: { ...opts, pick: true } });
|
|
65
82
|
const after = repos.map((r) => r.name);
|
package/src/commands/status.js
CHANGED
|
@@ -22,7 +22,7 @@ Columns
|
|
|
22
22
|
|
|
23
23
|
Options
|
|
24
24
|
-g, --group <names> comma-separated groups
|
|
25
|
-
-r, --repo <names> comma-separated repo names
|
|
25
|
+
-r, --repo <names> comma-separated repo names; a bare name works too
|
|
26
26
|
--drift only repos on some other branch
|
|
27
27
|
--missing only repos not cloned yet
|
|
28
28
|
--all ignore this machine's selection
|
package/src/commands/sync.js
CHANGED
|
@@ -23,6 +23,7 @@ ${c.bold('talea sync')} — make this machine match the list
|
|
|
23
23
|
|
|
24
24
|
${c.dim('talea sync')} clone what is missing, fast-forward the rest
|
|
25
25
|
${c.dim('talea sync --pick')} change what this machine keeps, then sync
|
|
26
|
+
${c.dim('talea sync eklavya')} only that repo (same as -r eklavya)
|
|
26
27
|
${c.dim('talea sync -g nonstopio')} only that owner
|
|
27
28
|
${c.dim('talea sync --no-clone')} fast-forward only, clone nothing
|
|
28
29
|
|
|
@@ -36,7 +37,7 @@ the workspace ${c.dim('talea init')} made, or asks which when this machine has s
|
|
|
36
37
|
|
|
37
38
|
Options
|
|
38
39
|
-g, --group <names> comma-separated groups
|
|
39
|
-
-r, --repo <names> comma-separated repo names
|
|
40
|
+
-r, --repo <names> comma-separated repo names; a bare name works too
|
|
40
41
|
--pick re-open the checklist before syncing
|
|
41
42
|
--no-clone do not clone anything new
|
|
42
43
|
--no-adopt do not look for checkouts to move into place
|
package/src/commands/tree.js
CHANGED
|
@@ -24,7 +24,7 @@ their docs come from the CLI — run \`talea sync\` to drop in any that are new.
|
|
|
24
24
|
|
|
25
25
|
Options
|
|
26
26
|
-g, --group <names> comma-separated groups
|
|
27
|
-
-r, --repo <names> comma-separated repo names
|
|
27
|
+
-r, --repo <names> comma-separated repo names; a bare name works too
|
|
28
28
|
--all the whole catalogue, not just what this machine keeps
|
|
29
29
|
`;
|
|
30
30
|
|
package/src/commands/upgrade.js
CHANGED
|
@@ -6,6 +6,7 @@ export const help = `
|
|
|
6
6
|
${c.bold('talea upgrade')} — update the CLI itself
|
|
7
7
|
|
|
8
8
|
${c.dim('talea upgrade')} reinstall from npm at the latest version
|
|
9
|
+
${c.dim('talea update')} the same thing
|
|
9
10
|
${c.dim('talea upgrade --check')} only report whether one is available
|
|
10
11
|
${c.dim('talea upgrade --on')} turn the daily update notice on
|
|
11
12
|
${c.dim('talea upgrade --off')} turn the daily update notice off
|
package/src/git.js
CHANGED
|
@@ -181,7 +181,11 @@ export async function currentBranch(dir) {
|
|
|
181
181
|
const { code, stdout } = await git(['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
182
182
|
cwd: dir,
|
|
183
183
|
});
|
|
184
|
-
|
|
184
|
+
if (code === 0) return stdout;
|
|
185
|
+
// A repo with no commits yet has no HEAD to resolve, so rev-parse fails and
|
|
186
|
+
// the caller printed "null". symbolic-ref still knows the branch's name.
|
|
187
|
+
const born = await git(['symbolic-ref', '--short', 'HEAD'], { cwd: dir });
|
|
188
|
+
return born.code === 0 ? born.stdout : null;
|
|
185
189
|
}
|
|
186
190
|
|
|
187
191
|
/** True when the working tree has uncommitted changes (tracked or untracked). */
|