@koder-0x/scopy 1.0.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/LICENSE +21 -0
- package/README.md +91 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +51 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/dest.d.ts +3 -0
- package/dist/commands/dest.d.ts.map +1 -0
- package/dist/commands/dest.js +54 -0
- package/dist/commands/dest.js.map +1 -0
- package/dist/commands/ghost.d.ts +4 -0
- package/dist/commands/ghost.d.ts.map +1 -0
- package/dist/commands/ghost.js +138 -0
- package/dist/commands/ghost.js.map +1 -0
- package/dist/commands/info.d.ts +4 -0
- package/dist/commands/info.d.ts.map +1 -0
- package/dist/commands/info.js +23 -0
- package/dist/commands/info.js.map +1 -0
- package/dist/commands/list.d.ts +3 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +27 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/log.d.ts +3 -0
- package/dist/commands/log.d.ts.map +1 -0
- package/dist/commands/log.js +62 -0
- package/dist/commands/log.js.map +1 -0
- package/dist/commands/purge.d.ts +9 -0
- package/dist/commands/purge.d.ts.map +1 -0
- package/dist/commands/purge.js +62 -0
- package/dist/commands/purge.js.map +1 -0
- package/dist/commands/resync.d.ts +8 -0
- package/dist/commands/resync.d.ts.map +1 -0
- package/dist/commands/resync.js +218 -0
- package/dist/commands/resync.js.map +1 -0
- package/dist/commands/source.d.ts +3 -0
- package/dist/commands/source.d.ts.map +1 -0
- package/dist/commands/source.js +80 -0
- package/dist/commands/source.js.map +1 -0
- package/dist/commands/sync.d.ts +14 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +288 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/config.d.ts +29 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +164 -0
- package/dist/config.js.map +1 -0
- package/dist/glob.d.ts +2 -0
- package/dist/glob.d.ts.map +1 -0
- package/dist/glob.js +5 -0
- package/dist/glob.js.map +1 -0
- package/dist/scopy.d.ts +3 -0
- package/dist/scopy.d.ts.map +1 -0
- package/dist/scopy.js +37 -0
- package/dist/scopy.js.map +1 -0
- package/dist/types.d.ts +47 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/ui.d.ts +12 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +37 -0
- package/dist/ui.js.map +1 -0
- package/dist/validate.d.ts +6 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +13 -0
- package/dist/validate.js.map +1 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Giacomo Stelluti Scala (zero.one.etc@gmail.com)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Super Copy
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@koder-0x/scopy)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](package.json)
|
|
6
|
+
|
|
7
|
+
A CLI tool for deploying files from registered sources to registered destinations, with tracking, re-sync, and ghost support (remove and restore tracked files).
|
|
8
|
+
|
|
9
|
+
Designed for single-user, local workflows: dotfiles, config files, Claude Code agents, or any asset you distribute across machines and projects.
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
## Why?
|
|
14
|
+
|
|
15
|
+
I maintain a set of [Claude Code agent definitions](https://github.com/gsscoder/claude-coding-agents) and reuse them across many projects — but every agent loaded eats context tokens, even when unused. I needed a way to make agents appear and disappear on demand without losing them. That's **ghost/unghost**: remove a tracked file (cached) when you don't need it, restore it instantly when you do.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npm install -g @koder-0x/scopy
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quickstart
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
# Register Claude Code agents repo and a project-level destination
|
|
27
|
+
scopy source add cc-agents https://github.com/gsscoder/claude-coding-agents
|
|
28
|
+
scopy dest add my-project /path/to/your/project/.claude/agents
|
|
29
|
+
|
|
30
|
+
# Sync all implement agents
|
|
31
|
+
scopy sync cc-agents/agents/implement/*.md my-project
|
|
32
|
+
|
|
33
|
+
# Re-sync after upstream updates
|
|
34
|
+
scopy resync my-project
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Ghost: make files disappear (and reappear)
|
|
38
|
+
|
|
39
|
+
Loaded agents you're not using right now still cost context tokens. `ghost` removes a tracked file from its destination (caching it first), `unghost` restores it — same command, toggled.
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
# Find a file's index, then ghost by index, filename, or wildcard
|
|
43
|
+
scopy log my-project
|
|
44
|
+
scopy ghost my-project 6
|
|
45
|
+
scopy ghost my-project task-builder.md
|
|
46
|
+
scopy ghost my-project task-*
|
|
47
|
+
|
|
48
|
+
# Or go interactive — all destinations and files in one grouped view
|
|
49
|
+
scopy ghost
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## How it works
|
|
53
|
+
|
|
54
|
+
Register sources (GitHub repos or local directories) and destinations (local directories), then sync files between them. GitHub sources are fetched directly via the GitHub API — no local Git installation required. Every copy is tracked so you can re-sync, inspect history, or ghost files without losing the originals.
|
|
55
|
+
|
|
56
|
+
## Commands
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
# Sources
|
|
60
|
+
scopy source add <name> <url|path> # register a git repo or local directory
|
|
61
|
+
scopy source remove <name> # remove a registered source
|
|
62
|
+
scopy source list # list registered sources
|
|
63
|
+
|
|
64
|
+
# Destinations
|
|
65
|
+
scopy dest add <name> <path> # register a local directory
|
|
66
|
+
scopy dest remove <name> # remove a registered destination
|
|
67
|
+
scopy dest list # list registered destinations
|
|
68
|
+
|
|
69
|
+
# Sync
|
|
70
|
+
scopy sync <source>[/<glob>] <dest> # copy files; existing files → interactive overwrite selector
|
|
71
|
+
scopy sync ... --force # overwrite all without prompting
|
|
72
|
+
scopy resync <dest> # re-copy all tracked files from their original sources
|
|
73
|
+
|
|
74
|
+
# History & state
|
|
75
|
+
scopy log [dest] # show copy history grouped by destination
|
|
76
|
+
scopy ghost [dest] [selector] # toggle file(s) ghosted/present; no args → interactive grouped view
|
|
77
|
+
scopy purge log <dest|*> # remove log entries (asks confirmation)
|
|
78
|
+
scopy purge log <dest|*> --force # remove log entries without prompting
|
|
79
|
+
|
|
80
|
+
# Config & info
|
|
81
|
+
scopy config [key] [value] # get or set preferences (e.g. sync.allowOverwrite)
|
|
82
|
+
scopy info # show config path and registered locations
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Requirements
|
|
86
|
+
|
|
87
|
+
- Node.js >= 20
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
MIT © [koder0x](https://github.com/gsscoder)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsDzC,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAK7D"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { getPrefs, getPref, setPref, dismissTip, PREF_KEYS } from '../config.js';
|
|
3
|
+
import { error as uiError } from '../ui.js';
|
|
4
|
+
const PREF_VALUES = {
|
|
5
|
+
'sync.allowOverwrite': ['true', 'false'],
|
|
6
|
+
};
|
|
7
|
+
function parseBoolValue(raw) {
|
|
8
|
+
return raw === 'true';
|
|
9
|
+
}
|
|
10
|
+
function formatValue(val) {
|
|
11
|
+
return String(val);
|
|
12
|
+
}
|
|
13
|
+
function handleConfig(key, value) {
|
|
14
|
+
if (key === undefined) {
|
|
15
|
+
const prefs = getPrefs();
|
|
16
|
+
for (const k of PREF_KEYS) {
|
|
17
|
+
console.log(`${k}=${chalk.cyan(formatValue(prefs[k]))}`);
|
|
18
|
+
}
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (!PREF_KEYS.includes(key)) {
|
|
22
|
+
uiError(`unknown key "${key}". Valid keys: ${PREF_KEYS.join(', ')}`);
|
|
23
|
+
process.exitCode = 1;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const typedKey = key;
|
|
27
|
+
if (value === undefined) {
|
|
28
|
+
console.log(`${key}=${chalk.cyan(formatValue(getPref(typedKey)))}`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const allowed = PREF_VALUES[typedKey];
|
|
32
|
+
if (!allowed.includes(value)) {
|
|
33
|
+
uiError(`invalid value "${value}" for "${key}". Allowed: ${allowed.join(', ')}`);
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (typedKey === 'sync.allowOverwrite') {
|
|
38
|
+
setPref(typedKey, parseBoolValue(value));
|
|
39
|
+
if (value === 'true') {
|
|
40
|
+
dismissTip('sync.allowOverwrite');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
console.log(`${key}=${chalk.cyan(value)}`);
|
|
44
|
+
}
|
|
45
|
+
export default function registerConfig(program) {
|
|
46
|
+
program
|
|
47
|
+
.command('config [key] [value]')
|
|
48
|
+
.description('Get or set configuration preferences')
|
|
49
|
+
.action(handleConfig);
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAgB,MAAM,cAAc,CAAC;AAC/F,OAAO,EAAE,KAAK,IAAI,OAAO,EAAE,MAAM,UAAU,CAAC;AAE5C,MAAM,WAAW,GAAuC;IACtD,qBAAqB,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;CACzC,CAAC;AAEF,SAAS,cAAc,CAAC,GAAW;IACjC,OAAO,GAAG,KAAK,MAAM,CAAC;AACxB,CAAC;AAED,SAAS,WAAW,CAAC,GAAY;IAC/B,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;AACrB,CAAC;AAED,SAAS,YAAY,CAAC,GAAuB,EAAE,KAAyB;IACtE,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;QACzB,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;YAC1B,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC3D,CAAC;QACD,OAAO;IACT,CAAC;IAED,IAAI,CAAE,SAA+B,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACpD,OAAO,CAAC,gBAAgB,GAAG,kBAAkB,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACrE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,OAAO;IACT,CAAC;IAED,MAAM,QAAQ,GAAG,GAAc,CAAC;IAEhC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,IAAI,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACpE,OAAO;IACT,CAAC;IAED,MAAM,OAAO,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IACtC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,kBAAkB,KAAK,UAAU,GAAG,eAAe,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjF,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,OAAO;IACT,CAAC;IAED,IAAI,QAAQ,KAAK,qBAAqB,EAAE,CAAC;QACvC,OAAO,CAAC,QAAQ,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;QACzC,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;YACrB,UAAU,CAAC,qBAAqB,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,OAAgB;IACrD,OAAO;SACJ,OAAO,CAAC,sBAAsB,CAAC;SAC/B,WAAW,CAAC,sCAAsC,CAAC;SACnD,MAAM,CAAC,YAAY,CAAC,CAAC;AAC1B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dest.d.ts","sourceRoot":"","sources":["../../src/commands/dest.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuCzC,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAsBvD"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { getDestinations, addDestination, removeDestination, destinationExists } from '../config.js';
|
|
3
|
+
import { validateLocalPath } from '../validate.js';
|
|
4
|
+
import { success, error, dim, printDestinationList } from '../ui.js';
|
|
5
|
+
function handleAdd(name, location) {
|
|
6
|
+
if (destinationExists(name)) {
|
|
7
|
+
error(`destination "${name}" already exists`);
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const result = validateLocalPath(location);
|
|
11
|
+
if (!result.valid) {
|
|
12
|
+
error(result.error ?? 'invalid local path');
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
addDestination({ name, location: path.resolve(location) });
|
|
16
|
+
success(`Destination "${name}" added`);
|
|
17
|
+
}
|
|
18
|
+
function handleRemove(name) {
|
|
19
|
+
if (!destinationExists(name)) {
|
|
20
|
+
error(`destination "${name}" not found`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
removeDestination(name);
|
|
24
|
+
success(`Destination "${name}" removed`);
|
|
25
|
+
}
|
|
26
|
+
function handleList() {
|
|
27
|
+
const destinations = getDestinations();
|
|
28
|
+
if (destinations.length === 0) {
|
|
29
|
+
dim('No destinations registered');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
printDestinationList(destinations);
|
|
33
|
+
}
|
|
34
|
+
export default function register(program) {
|
|
35
|
+
const dest = program
|
|
36
|
+
.command('dest')
|
|
37
|
+
.description('Manage asset destinations');
|
|
38
|
+
dest
|
|
39
|
+
.command('add')
|
|
40
|
+
.description('Add a destination (local path)')
|
|
41
|
+
.argument('<name>', 'Destination name')
|
|
42
|
+
.argument('<location>', 'Local filesystem path')
|
|
43
|
+
.action(handleAdd);
|
|
44
|
+
dest
|
|
45
|
+
.command('remove')
|
|
46
|
+
.description('Remove a destination by name')
|
|
47
|
+
.argument('<name>', 'Destination name')
|
|
48
|
+
.action(handleRemove);
|
|
49
|
+
dest
|
|
50
|
+
.command('list')
|
|
51
|
+
.description('List all registered destinations')
|
|
52
|
+
.action(handleList);
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=dest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dest.js","sourceRoot":"","sources":["../../src/commands/dest.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACrG,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAS,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAE5E,SAAS,SAAS,CAAC,IAAY,EAAE,QAAgB;IAC/C,IAAI,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5B,KAAK,CAAC,gBAAgB,IAAI,kBAAkB,CAAC,CAAC;QAC9C,OAAO;IACT,CAAC;IAED,MAAM,MAAM,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAC3C,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAClB,KAAK,CAAC,MAAM,CAAC,KAAK,IAAI,oBAAoB,CAAC,CAAC;QAC5C,OAAO;IACT,CAAC;IAED,cAAc,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAC3D,OAAO,CAAC,gBAAgB,IAAI,SAAS,CAAC,CAAC;AACzC,CAAC;AAED,SAAS,YAAY,CAAC,IAAY;IAChC,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,KAAK,CAAC,gBAAgB,IAAI,aAAa,CAAC,CAAC;QACzC,OAAO;IACT,CAAC;IACD,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACxB,OAAO,CAAC,gBAAgB,IAAI,WAAW,CAAC,CAAC;AAC3C,CAAC;AAED,SAAS,UAAU;IACjB,MAAM,YAAY,GAAG,eAAe,EAAE,CAAC;IACvC,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,GAAG,CAAC,4BAA4B,CAAC,CAAC;QAClC,OAAO;IACT,CAAC;IACD,oBAAoB,CAAC,YAAY,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,OAAgB;IAC/C,MAAM,IAAI,GAAG,OAAO;SACjB,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,2BAA2B,CAAC,CAAC;IAE5C,IAAI;SACD,OAAO,CAAC,KAAK,CAAC;SACd,WAAW,CAAC,gCAAgC,CAAC;SAC7C,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;SACtC,QAAQ,CAAC,YAAY,EAAE,uBAAuB,CAAC;SAC/C,MAAM,CAAC,SAAS,CAAC,CAAC;IAErB,IAAI;SACD,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,8BAA8B,CAAC;SAC3C,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;SACtC,MAAM,CAAC,YAAY,CAAC,CAAC;IAExB,IAAI;SACD,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,kCAAkC,CAAC;SAC/C,MAAM,CAAC,UAAU,CAAC,CAAC;AACxB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ghost.d.ts","sourceRoot":"","sources":["../../src/commands/ghost.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+GzC,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CA0CvG;AAED,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAOvD"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import checkbox, { Separator } from '@inquirer/checkbox';
|
|
5
|
+
import { destinationExists, getDestinations, getCopyByIndex, getCopiesByDestination, setGhosted, fileCachePath, addCopy, } from '../config.js';
|
|
6
|
+
import { error as uiError, dim } from '../ui.js';
|
|
7
|
+
import { globPattern } from '../glob.js';
|
|
8
|
+
function toggleRecord(dest, destLocation, record) {
|
|
9
|
+
if (record.index === undefined) {
|
|
10
|
+
uiError(`record for "${record.file}" has no index — skipping`);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const destPath = path.join(destLocation, record.file);
|
|
14
|
+
if (record.ghosted === false || record.ghosted === undefined) {
|
|
15
|
+
const cachePath = fileCachePath(dest, record.index);
|
|
16
|
+
if (!fs.existsSync(cachePath)) {
|
|
17
|
+
dim(`cache not found for "${record.file}" — proceeding without cache backup`);
|
|
18
|
+
}
|
|
19
|
+
fs.rmSync(destPath, { force: true });
|
|
20
|
+
setGhosted(dest, record.index, true);
|
|
21
|
+
console.log(`${chalk.red('ghosted')} ${record.file} → removed and cached`);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
const cachePath = fileCachePath(dest, record.index);
|
|
25
|
+
if (!fs.existsSync(cachePath)) {
|
|
26
|
+
uiError(`cache not found for "${record.file}" — cannot restore`);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
30
|
+
fs.copyFileSync(cachePath, destPath);
|
|
31
|
+
setGhosted(dest, record.index, false);
|
|
32
|
+
addCopy({
|
|
33
|
+
source: record.source,
|
|
34
|
+
destination: dest,
|
|
35
|
+
file: record.file,
|
|
36
|
+
copiedAt: new Date().toISOString(),
|
|
37
|
+
});
|
|
38
|
+
console.log(`${chalk.green('unghosted')} ${record.file} ← restored from cache`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function handleGhostInteractive(destFilter) {
|
|
42
|
+
const destinations = destFilter === undefined
|
|
43
|
+
? getDestinations()
|
|
44
|
+
: getDestinations().filter((d) => d.name === destFilter);
|
|
45
|
+
const withFiles = destinations.filter((d) => getCopiesByDestination(d.name).length > 0);
|
|
46
|
+
if (withFiles.length === 0) {
|
|
47
|
+
dim(destFilter === undefined ? 'no destinations with tracked files' : `no tracked files for destination "${destFilter}"`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const choices = [];
|
|
51
|
+
const allRecords = new Map();
|
|
52
|
+
for (const d of withFiles) {
|
|
53
|
+
const records = getCopiesByDestination(d.name);
|
|
54
|
+
allRecords.set(d.name, { dest: d.name, location: d.location, records });
|
|
55
|
+
choices.push(new Separator(chalk.bold(d.name)));
|
|
56
|
+
for (const r of records) {
|
|
57
|
+
choices.push({
|
|
58
|
+
name: r.file,
|
|
59
|
+
value: { dest: d.name, file: r.file },
|
|
60
|
+
checked: r.ghosted === true,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const chosen = await checkbox({
|
|
65
|
+
message: '',
|
|
66
|
+
choices,
|
|
67
|
+
loop: false,
|
|
68
|
+
theme: {
|
|
69
|
+
icon: {
|
|
70
|
+
checked: chalk.red('● [ghosted] '),
|
|
71
|
+
unchecked: chalk.green('○ [unghosted] '),
|
|
72
|
+
cursor: '›',
|
|
73
|
+
},
|
|
74
|
+
prefix: { idle: '', done: '' },
|
|
75
|
+
style: { answer: () => '', message: () => '' },
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
const nowGhosted = new Set(chosen.map((c) => `${c.dest}::${c.file}`));
|
|
79
|
+
for (const { dest, location, records } of allRecords.values()) {
|
|
80
|
+
for (const record of records) {
|
|
81
|
+
const key = `${dest}::${record.file}`;
|
|
82
|
+
const wasGhosted = record.ghosted === true;
|
|
83
|
+
const shouldBeGhosted = nowGhosted.has(key);
|
|
84
|
+
if (wasGhosted !== shouldBeGhosted) {
|
|
85
|
+
toggleRecord(dest, location, record);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export async function handleGhost(dest, selector) {
|
|
91
|
+
if (dest === undefined) {
|
|
92
|
+
await handleGhostInteractive();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (!destinationExists(dest)) {
|
|
96
|
+
uiError(`destination "${dest}" is not registered`);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (selector === undefined) {
|
|
100
|
+
await handleGhostInteractive(dest);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const destinations = getDestinations();
|
|
104
|
+
const destination = destinations.find((d) => d.name === dest);
|
|
105
|
+
if (destination === undefined) {
|
|
106
|
+
uiError(`internal error: destination "${dest}" not found after validation`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const index = parseInt(selector, 10);
|
|
110
|
+
if (!isNaN(index)) {
|
|
111
|
+
const record = getCopyByIndex(dest, index);
|
|
112
|
+
if (record === undefined) {
|
|
113
|
+
uiError(`no file with index ${index} for destination "${dest}"`);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
toggleRecord(dest, destination.location, record);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
const regex = globPattern(selector);
|
|
120
|
+
const matches = getCopiesByDestination(dest).filter((r) => regex.test(r.file));
|
|
121
|
+
if (matches.length === 0) {
|
|
122
|
+
dim(`no matching files for "${selector}"`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
for (const record of matches) {
|
|
126
|
+
toggleRecord(dest, destination.location, record);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
export default function register(program) {
|
|
131
|
+
program
|
|
132
|
+
.command('ghost')
|
|
133
|
+
.description('Toggle tracked file(s) between present and ghosted (cached); no args = interactive')
|
|
134
|
+
.argument('[dest]', 'Destination name')
|
|
135
|
+
.argument('[selector]', 'File index, filename, or wildcard pattern (e.g. task-*)')
|
|
136
|
+
.action(handleGhost);
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=ghost.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ghost.js","sourceRoot":"","sources":["../../src/commands/ghost.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"info.d.ts","sourceRoot":"","sources":["../../src/commands/info.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAiB/C,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,WAAW,GAAG,IAAI,CAKzE"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import figlet from 'figlet';
|
|
4
|
+
import { configDir, dataPath } from '../config.js';
|
|
5
|
+
import { keyValue } from '../ui.js';
|
|
6
|
+
function handleInfo(pkg) {
|
|
7
|
+
const configPath = path.join(configDir, 'scopy.json');
|
|
8
|
+
const repoPath = path.join(dataPath, 'repos');
|
|
9
|
+
const registerPath = path.join(dataPath, 'scopy-register.json');
|
|
10
|
+
console.log(chalk.cyan(figlet.textSync('scopy', { font: 'Small Slant' }).replace(/^\n+/, '\n')));
|
|
11
|
+
console.log();
|
|
12
|
+
console.log(`${chalk.bold.white(pkg.name)} ${chalk.dim(`v${pkg.version}`)}`);
|
|
13
|
+
keyValue('config', configPath);
|
|
14
|
+
keyValue('clone path', repoPath);
|
|
15
|
+
keyValue('register', registerPath);
|
|
16
|
+
}
|
|
17
|
+
export default function register(program, pkg) {
|
|
18
|
+
program
|
|
19
|
+
.command('info')
|
|
20
|
+
.description('Display configuration and environment information')
|
|
21
|
+
.action(() => handleInfo(pkg));
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=info.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"info.js","sourceRoot":"","sources":["../../src/commands/info.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,MAAM,MAAM,QAAQ,CAAC;AAG5B,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEpC,SAAS,UAAU,CAAC,GAAgB;IAClC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IACtD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC9C,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,qBAAqB,CAAC,CAAC;IAEhE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IACjG,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAA;IAC5E,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAC/B,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,OAAgB,EAAE,GAAgB;IACjE,OAAO;SACJ,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,mDAAmD,CAAC;SAChE,MAAM,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;AACnC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuBzC,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAKvD"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { getSources, getDestinations } from '../config.js';
|
|
2
|
+
import { heading, dim, printSourceList, printDestinationList } from '../ui.js';
|
|
3
|
+
function handleList() {
|
|
4
|
+
const sources = getSources();
|
|
5
|
+
const destinations = getDestinations();
|
|
6
|
+
heading('sources');
|
|
7
|
+
if (sources.length === 0) {
|
|
8
|
+
dim('no sources registered');
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
printSourceList(sources);
|
|
12
|
+
}
|
|
13
|
+
heading('destinations');
|
|
14
|
+
if (destinations.length === 0) {
|
|
15
|
+
dim('no destinations registered');
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
printDestinationList(destinations);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export default function register(program) {
|
|
22
|
+
program
|
|
23
|
+
.command('list')
|
|
24
|
+
.description('List all registered sources and destinations')
|
|
25
|
+
.action(handleList);
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAE/E,SAAS,UAAU;IACjB,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,MAAM,YAAY,GAAG,eAAe,EAAE,CAAC;IAEvC,OAAO,CAAC,SAAS,CAAC,CAAC;IACnB,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,GAAG,CAAC,uBAAuB,CAAC,CAAC;IAC/B,CAAC;SAAM,CAAC;QACN,eAAe,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;IAED,OAAO,CAAC,cAAc,CAAC,CAAC;IACxB,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,GAAG,CAAC,4BAA4B,CAAC,CAAC;IACpC,CAAC;SAAM,CAAC;QACN,oBAAoB,CAAC,YAAY,CAAC,CAAC;IACrC,CAAC;AACH,CAAC;AAED,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,OAAgB;IAC/C,OAAO;SACJ,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,8CAA8C,CAAC;SAC3D,MAAM,CAAC,UAAU,CAAC,CAAC;AACxB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../../src/commands/log.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuEzC,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAKvD"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { getCopies, getCopiesByDestination, getDestinations, destinationExists, } from '../config.js';
|
|
2
|
+
import { heading, dim, blank, error } from '../ui.js';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
function printDestGroup(destName, location, records) {
|
|
5
|
+
heading(`${destName} ${chalk.gray('(' + location + ')')}`);
|
|
6
|
+
if (records.length === 0) {
|
|
7
|
+
dim('no tracked files');
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const indexWidth = String(Math.max(...records.map((r) => r.index ?? 0), records.length)).length;
|
|
11
|
+
const nameWidth = Math.max(...records.map((r) => r.file.length));
|
|
12
|
+
for (const r of records) {
|
|
13
|
+
const idx = String(r.index ?? '?').padStart(indexWidth);
|
|
14
|
+
const name = r.file.padEnd(nameWidth);
|
|
15
|
+
const ts = chalk.dim(r.copiedAt ?? '');
|
|
16
|
+
const ghosted = r.ghosted ? chalk.red(' [ghosted]') : chalk.green(' [unghosted]');
|
|
17
|
+
console.log(` ${idx} ${name} ${ts}${ghosted}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function handleLog(dest) {
|
|
21
|
+
if (dest !== undefined) {
|
|
22
|
+
if (!destinationExists(dest)) {
|
|
23
|
+
error(`destination '${dest}' is not registered`);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
const copies = getCopiesByDestination(dest);
|
|
27
|
+
const destinations = getDestinations();
|
|
28
|
+
const destObj = destinations.find((d) => d.name === dest);
|
|
29
|
+
printDestGroup(dest, destObj?.location ?? '', copies);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const destinations = getDestinations();
|
|
33
|
+
const allCopies = getCopies();
|
|
34
|
+
if (destinations.length === 0) {
|
|
35
|
+
dim('no destinations registered');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const withFiles = destinations.filter((d) => allCopies.some((c) => c.destination === d.name));
|
|
39
|
+
const withoutFiles = destinations.filter((d) => !allCopies.some((c) => c.destination === d.name));
|
|
40
|
+
for (let i = 0; i < withFiles.length; i++) {
|
|
41
|
+
const d = withFiles[i];
|
|
42
|
+
const copies = allCopies.filter((c) => c.destination === d.name);
|
|
43
|
+
printDestGroup(d.name, d.location, copies);
|
|
44
|
+
if (i < withFiles.length - 1)
|
|
45
|
+
blank();
|
|
46
|
+
}
|
|
47
|
+
if (withoutFiles.length > 0) {
|
|
48
|
+
if (withFiles.length > 0)
|
|
49
|
+
blank();
|
|
50
|
+
heading('not yet synced');
|
|
51
|
+
for (const d of withoutFiles) {
|
|
52
|
+
console.log(` ${chalk.gray(d.name)}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export default function register(program) {
|
|
57
|
+
program
|
|
58
|
+
.command('log [dest]')
|
|
59
|
+
.description('Show tracked files grouped by destination')
|
|
60
|
+
.action(handleLog);
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.js","sourceRoot":"","sources":["../../src/commands/log.ts"],"names":[],"mappings":"AACA,OAAO,EACL,SAAS,EACT,sBAAsB,EACtB,eAAe,EACf,iBAAiB,GAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAEtD,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,SAAS,cAAc,CAAC,QAAgB,EAAE,QAAgB,EAAE,OAAqB;IAC/E,OAAO,CAAC,GAAG,QAAQ,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,GAAG,QAAQ,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC;IAE3D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,GAAG,CAAC,kBAAkB,CAAC,CAAC;QACxB,OAAO;IACT,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;IAChG,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAEjE,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;QACxD,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACtC,MAAM,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;QACvC,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;QAClF,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,KAAK,IAAI,KAAK,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;IACpD,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAAC,IAAwB;IACzC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7B,KAAK,CAAC,gBAAgB,IAAI,qBAAqB,CAAC,CAAC;YACjD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,MAAM,MAAM,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC;QAC5C,MAAM,YAAY,GAAG,eAAe,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;QAC1D,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC;QACtD,OAAO;IACT,CAAC;IAED,MAAM,YAAY,GAAG,eAAe,EAAE,CAAC;IACvC,MAAM,SAAS,GAAG,SAAS,EAAE,CAAC;IAE9B,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,GAAG,CAAC,4BAA4B,CAAC,CAAC;QAClC,OAAO;IACT,CAAC;IAED,MAAM,SAAS,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAC9F,MAAM,YAAY,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAElG,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1C,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;QACvB,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC;QACjE,cAAc,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC3C,IAAI,CAAC,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC;YAAE,KAAK,EAAE,CAAC;IACxC,CAAC;IAED,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC;YAAE,KAAK,EAAE,CAAC;QAClC,OAAO,CAAC,gBAAgB,CAAC,CAAC;QAC1B,KAAK,MAAM,CAAC,IAAI,YAAY,EAAE,CAAC;YAC7B,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,OAAgB;IAC/C,OAAO;SACJ,OAAO,CAAC,YAAY,CAAC;SACrB,WAAW,CAAC,2CAA2C,CAAC;SACxD,MAAM,CAAC,SAAS,CAAC,CAAC;AACvB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
interface PurgeLogOptions {
|
|
3
|
+
dryRun: boolean;
|
|
4
|
+
force: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare function handlePurgeLog(dest: string | undefined, opts: PurgeLogOptions): Promise<void>;
|
|
7
|
+
export default function registerPurge(program: Command): void;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=purge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"purge.d.ts","sourceRoot":"","sources":["../../src/commands/purge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,UAAU,eAAe;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;CAChB;AAkCD,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBnG;AAED,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAW5D"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import readline from 'node:readline';
|
|
3
|
+
import { getCopies, purgeCopies } from '../config.js';
|
|
4
|
+
import { error as uiError, dim } from '../ui.js';
|
|
5
|
+
function confirm(msg) {
|
|
6
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
7
|
+
return new Promise((resolve) => {
|
|
8
|
+
rl.question(msg, (answer) => {
|
|
9
|
+
rl.close();
|
|
10
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
function allPredicate() {
|
|
15
|
+
return () => true;
|
|
16
|
+
}
|
|
17
|
+
function byDestinationPredicate(destName) {
|
|
18
|
+
return (r) => r.destination === destName;
|
|
19
|
+
}
|
|
20
|
+
function applyPurge(predicate, dryRun) {
|
|
21
|
+
if (dryRun) {
|
|
22
|
+
const candidates = getCopies().filter(predicate);
|
|
23
|
+
console.log(`Would remove ${candidates.length} entr${candidates.length === 1 ? 'y' : 'ies'}:`);
|
|
24
|
+
for (const r of candidates) {
|
|
25
|
+
dim(`· ${r.file} (${r.source} → ${r.destination})`);
|
|
26
|
+
}
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const removed = purgeCopies(predicate);
|
|
30
|
+
console.log(`${removed} entr${removed === 1 ? 'y' : 'ies'} removed`);
|
|
31
|
+
}
|
|
32
|
+
export async function handlePurgeLog(dest, opts) {
|
|
33
|
+
if (dest === undefined) {
|
|
34
|
+
uiError('specify a destination name or * for all');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const predicate = dest === '*' ? allPredicate() : byDestinationPredicate(dest);
|
|
38
|
+
if (!opts.dryRun && !opts.force) {
|
|
39
|
+
const count = getCopies().filter(predicate).length;
|
|
40
|
+
if (count === 0) {
|
|
41
|
+
dim('nothing to purge');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const ok = await confirm(`Remove ${count} entr${count === 1 ? 'y' : 'ies'}? (y/N) `);
|
|
45
|
+
if (!ok) {
|
|
46
|
+
dim('aborted');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
applyPurge(predicate, opts.dryRun);
|
|
51
|
+
}
|
|
52
|
+
export default function registerPurge(program) {
|
|
53
|
+
const purge = new Command('purge').description('Purge cached data');
|
|
54
|
+
purge
|
|
55
|
+
.command('log [dest]')
|
|
56
|
+
.description('Remove copy log entries by destination (* = all, <name> = named dest) or by age (--older-than)')
|
|
57
|
+
.option('--dry-run', 'Show what would be removed without modifying the registry')
|
|
58
|
+
.option('--force', 'Skip confirmation prompt')
|
|
59
|
+
.action((dest, opts) => handlePurgeLog(dest, opts));
|
|
60
|
+
program.addCommand(purge);
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=purge.js.map
|