@papillon6814/copi 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/src/group-picker.d.ts +6 -1
- package/dist/src/group-picker.js +4 -2
- package/dist/src/wizard.js +13 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ copi help
|
|
|
31
31
|
|
|
32
32
|
Candidates are listed with a source label, size and modification date, and entries whose name matches an already saved resource are marked `already saved`. After you select resources, name conflicts are resolved right away, before any preview: keep one of the same-named selections, rename the display name only (the files, body and destination are not changed), skip the resource (keeping the saved copy), or go back to the list.
|
|
33
33
|
|
|
34
|
-
A group is a named set of saved resources that you later apply together with `copi apply <group> <project>`. If no group exists yet, setup asks you to create the first one. With several resources, setup asks once how to group them: by default all of them go into the same group(s); you can instead choose groups for each resource. The group list itself ends with **+ New group…** and **✎ Rename or delete groups…**: create, rename or delete groups right there and return to the list with your selection kept. Deleting a group never deletes resources; saved resources only leave that group, and a group that holds saved resources or resources already assigned in this run asks first (default No). Like everything else in setup, group changes are written only when you answer Yes at the final confirmation. Each resource is then reviewed in turn with a masked preview titled with its progress (for example `1/3`) and its source directory. A suspected sensitive file needs its own approval; answering No means that resource is not saved at all. Finally, one confirmation lists everything that will be copied into the library; the original files are never changed. When it is done, setup prints the next step, for example `copi apply <group> <project> --dry-run`.
|
|
34
|
+
A group is a named set of saved resources that you later apply together with `copi apply <group> <project>`. If no group exists yet, setup asks you to create the first one. With several resources, setup asks once how to group them: by default all of them go into the same group(s); you can instead choose groups for each resource; that list shows how many are left (for example `2/5 · 3 left`) and ends with **↷ Skip**, which leaves the resource out of this run. The group list itself ends with **+ New group…** and **✎ Rename or delete groups…**: create, rename or delete groups right there and return to the list with your selection kept. Deleting a group never deletes resources; saved resources only leave that group, and a group that holds saved resources or resources already assigned in this run asks first (default No). Like everything else in setup, group changes are written only when you answer Yes at the final confirmation. Each resource is then reviewed in turn with a masked preview titled with its progress (for example `1/3`) and its source directory. A suspected sensitive file needs its own approval; answering No means that resource is not saved at all. Finally, one confirmation lists everything that will be copied into the library; the original files are never changed. When it is done, setup prints the next step, for example `copi apply <group> <project> --dry-run`.
|
|
35
35
|
|
|
36
36
|
`add` is the non-interactive way to register resources, meant for scripts and AI agents (for example, tell your agent to run `copi add .pi/skills/my-skill --group game --json`). It reads only the paths you name and saves each one into the named group, creating the group if needed. The kind is detected (a directory with `SKILL.md` is a skill, `agents/*.md` a subagent, `AGENTS.md`/`CLAUDE.md`/`SYSTEM.md` an instruction) or set with `--kind`. A path is refused, and left for a person to review with `copi setup`, when its inspection finds suspected sensitive content, when it is or (for a skill directory) contains a settings or credential file (`settings.json`, `mcp.json`, `.mcp.json`, `models.json`, `keybindings.json`, SSH keys, `*.pem`, `.netrc` and similar; names compared case-insensitively), when the path or any parent directory is a symbolic link, when it lies under an excluded folder (`auth`, `.env*`, caches, history, sessions, `.git`, `node_modules`) or inside the copi library, when an `instruction`/`subagent` is not a Markdown file, or when its display name is already used (`--name` sets another). Each path gets its own result; if saving fails midway, the remaining paths are reported as errors and a group created by that command is removed again when it stayed empty. JSON output is not passed through display masking, so treat it as data. `--dry-run` reports without saving and `--json` prints machine-readable results; the exit status is 1 if any path was refused. Because secret detection is heuristic, a secret it misses is saved like any other content, without the per-file approval `setup` asks for.
|
|
37
37
|
|
|
@@ -9,7 +9,7 @@ export interface GroupRow {
|
|
|
9
9
|
}
|
|
10
10
|
/** `groupIds` is always the current selection, so an action can return to the list with it intact. */
|
|
11
11
|
export type GroupPickerResult = {
|
|
12
|
-
kind: "assign" | "new" | "edit";
|
|
12
|
+
kind: "assign" | "new" | "edit" | "skip";
|
|
13
13
|
groupIds: string[];
|
|
14
14
|
filter: string;
|
|
15
15
|
};
|
|
@@ -18,6 +18,8 @@ export interface GroupPickerOptions {
|
|
|
18
18
|
message: string;
|
|
19
19
|
groups: readonly GroupRow[];
|
|
20
20
|
initialValues?: readonly string[];
|
|
21
|
+
/** Adds a last row that skips this resource (it is not saved). */
|
|
22
|
+
allowSkip?: boolean;
|
|
21
23
|
signal?: AbortSignal;
|
|
22
24
|
input?: NodeJS.ReadableStream;
|
|
23
25
|
output?: NodeJS.WritableStream;
|
|
@@ -29,9 +31,12 @@ type Row = {
|
|
|
29
31
|
kind: "new";
|
|
30
32
|
} | {
|
|
31
33
|
kind: "edit";
|
|
34
|
+
} | {
|
|
35
|
+
kind: "skip";
|
|
32
36
|
};
|
|
33
37
|
export declare const newGroupLabel = "\uFF0B New group\u2026";
|
|
34
38
|
export declare const editGroupsLabel = "\u270E Rename or delete groups\u2026";
|
|
39
|
+
export declare const skipLabel = "\u21B7 Skip \u2014 don't save this resource";
|
|
35
40
|
/**
|
|
36
41
|
* Multi-select of groups with two action rows at the end. ↑/↓ move, Tab or Space toggles, typing filters groups,
|
|
37
42
|
* Enter on a group row confirms the selection (at least one), Enter on an action row returns that action.
|
package/dist/src/group-picker.js
CHANGED
|
@@ -5,6 +5,7 @@ import { styleText } from "node:util";
|
|
|
5
5
|
import { clip } from "./resource-picker.js";
|
|
6
6
|
export const newGroupLabel = "+ New group…";
|
|
7
7
|
export const editGroupsLabel = "✎ Rename or delete groups…";
|
|
8
|
+
export const skipLabel = "↷ Skip \u2014 don't save this resource";
|
|
8
9
|
/**
|
|
9
10
|
* Multi-select of groups with two action rows at the end. ↑/↓ move, Tab or Space toggles, typing filters groups,
|
|
10
11
|
* Enter on a group row confirms the selection (at least one), Enter on an action row returns that action.
|
|
@@ -37,6 +38,7 @@ export class GroupPickerPrompt extends Prompt {
|
|
|
37
38
|
...groups.map((group) => ({ kind: "group", group })),
|
|
38
39
|
{ kind: "new" },
|
|
39
40
|
...(this.options.groups.length > 0 ? [{ kind: "edit" }] : []),
|
|
41
|
+
...(this.options.allowSkip ? [{ kind: "skip" }] : []),
|
|
40
42
|
];
|
|
41
43
|
}
|
|
42
44
|
updateValue() {
|
|
@@ -75,7 +77,7 @@ export class GroupPickerPrompt extends Prompt {
|
|
|
75
77
|
const title = `${symbol(this.state)} ${this.options.message}`;
|
|
76
78
|
const chosen = this.options.groups.filter((group) => this.selected.has(group.id)).map((group) => group.label).join(", ");
|
|
77
79
|
if (this.state === "submit") {
|
|
78
|
-
const summary = this.value?.kind === "assign" ? chosen : this.value?.kind === "new" ? "New group" : "Rename or delete groups";
|
|
80
|
+
const summary = this.value?.kind === "assign" ? chosen : this.value?.kind === "new" ? "New group" : this.value?.kind === "skip" ? "Skipped" : "Rename or delete groups";
|
|
79
81
|
return `${styleText("gray", S_BAR)}\n${title}\n${styleText("gray", S_BAR)} ${styleText("dim", clip(summary, width))}`;
|
|
80
82
|
}
|
|
81
83
|
if (this.state === "cancel")
|
|
@@ -102,7 +104,7 @@ export class GroupPickerPrompt extends Prompt {
|
|
|
102
104
|
lines.push(`${bar} ${box} ${focused ? styleText("underline", label) : label}${room >= 4 ? ` ${styleText("dim", clip(hint, room))}` : ""}`);
|
|
103
105
|
}
|
|
104
106
|
else {
|
|
105
|
-
const label = row.kind === "new" ? newGroupLabel : editGroupsLabel;
|
|
107
|
+
const label = row.kind === "new" ? newGroupLabel : row.kind === "edit" ? editGroupsLabel : skipLabel;
|
|
106
108
|
lines.push(`${bar} ${focused ? styleText(["cyan", "underline"], label) : styleText("dim", label)}`);
|
|
107
109
|
}
|
|
108
110
|
}
|
package/dist/src/wizard.js
CHANGED
|
@@ -287,9 +287,9 @@ function groupOptions(groups, pendingCounts) {
|
|
|
287
287
|
/**
|
|
288
288
|
* The setup assignment list: pick groups, or create, rename and delete groups in place and come back with the
|
|
289
289
|
* selection kept. Deleting also drops the group from resources already assigned in this setup.
|
|
290
|
-
* Returns undefined when the person cancels the list itself.
|
|
290
|
+
* Returns undefined when the person cancels the list itself, "skip" when they skip the resource (allowSkip only).
|
|
291
291
|
*/
|
|
292
|
-
async function chooseGroups(catalog, message, initialValues, assigned) {
|
|
292
|
+
async function chooseGroups(catalog, message, initialValues, assigned, allowSkip = false) {
|
|
293
293
|
let selection = initialValues;
|
|
294
294
|
while (true) {
|
|
295
295
|
const pendingCounts = new Map();
|
|
@@ -300,12 +300,15 @@ async function chooseGroups(catalog, message, initialValues, assigned) {
|
|
|
300
300
|
message: `${safeDisplay(message)} ${multiselectKeyHint}`,
|
|
301
301
|
groups: groupOptions(catalog.groups, pendingCounts).map((option) => ({ id: option.value, label: safeDisplay(option.label), hint: safeDisplay(option.hint) })),
|
|
302
302
|
initialValues: selection,
|
|
303
|
+
allowSkip,
|
|
303
304
|
...inheritedSignal(),
|
|
304
305
|
});
|
|
305
306
|
if (isCancel(result))
|
|
306
307
|
return undefined;
|
|
307
308
|
if (result.kind === "assign")
|
|
308
309
|
return result.groupIds;
|
|
310
|
+
if (result.kind === "skip")
|
|
311
|
+
return "skip";
|
|
309
312
|
selection = result.groupIds;
|
|
310
313
|
if (result.kind === "new") {
|
|
311
314
|
// Esc at the name goes back to the list rather than ending setup.
|
|
@@ -701,7 +704,7 @@ export async function runSetup(libraryDir, roots) {
|
|
|
701
704
|
const count = selectedCandidates.length;
|
|
702
705
|
const onlyGroup = catalog.groups.length === 1 ? catalog.groups[0] : undefined;
|
|
703
706
|
// Groups can be created, renamed and deleted from the assignment list; nothing reaches disk before the final Yes.
|
|
704
|
-
const pickGroups = (message, initialValues) => chooseGroups(catalog, message, initialValues, approvedCandidates);
|
|
707
|
+
const pickGroups = (message, initialValues, allowSkip = false) => chooseGroups(catalog, message, initialValues, approvedCandidates, allowSkip);
|
|
705
708
|
let bulk = count === 1;
|
|
706
709
|
if (count >= 2) {
|
|
707
710
|
const mode = await select({
|
|
@@ -720,7 +723,7 @@ export async function runSetup(libraryDir, roots) {
|
|
|
720
723
|
}
|
|
721
724
|
if (bulk) {
|
|
722
725
|
const chosen = await pickGroups(count === 1 ? `Assign ${selectedCandidates[0].name} to one or more groups` : `Put all ${count} resources in these groups`, onlyGroup ? [onlyGroup.id] : []);
|
|
723
|
-
if (chosen === undefined) {
|
|
726
|
+
if (chosen === undefined || chosen === "skip") {
|
|
724
727
|
cancel("Setup cancelled; no resources were saved.");
|
|
725
728
|
return;
|
|
726
729
|
}
|
|
@@ -759,11 +762,16 @@ export async function runSetup(libraryDir, roots) {
|
|
|
759
762
|
approvedCandidates.push({ review, groupIds: bulkGroupIds, approvedSensitivePaths });
|
|
760
763
|
continue;
|
|
761
764
|
}
|
|
762
|
-
const
|
|
765
|
+
const left = count - index - 1;
|
|
766
|
+
const groupIds = await pickGroups(`Assign ${review.candidate.name} to one or more groups (${index + 1}/${count} \u00b7 ${left} left)`, [], true);
|
|
763
767
|
if (groupIds === undefined) {
|
|
764
768
|
cancel("Setup cancelled; nothing was saved.");
|
|
765
769
|
return;
|
|
766
770
|
}
|
|
771
|
+
if (groupIds === "skip") {
|
|
772
|
+
log.info(safeDisplay(`Skipped ${review.candidate.name}; it will not be saved.`));
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
767
775
|
approvedCandidates.push({ review, groupIds, approvedSensitivePaths });
|
|
768
776
|
}
|
|
769
777
|
if (approvedCandidates.length === 0) {
|