@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.
Files changed (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +91 -0
  3. package/dist/commands/config.d.ts +3 -0
  4. package/dist/commands/config.d.ts.map +1 -0
  5. package/dist/commands/config.js +51 -0
  6. package/dist/commands/config.js.map +1 -0
  7. package/dist/commands/dest.d.ts +3 -0
  8. package/dist/commands/dest.d.ts.map +1 -0
  9. package/dist/commands/dest.js +54 -0
  10. package/dist/commands/dest.js.map +1 -0
  11. package/dist/commands/ghost.d.ts +4 -0
  12. package/dist/commands/ghost.d.ts.map +1 -0
  13. package/dist/commands/ghost.js +138 -0
  14. package/dist/commands/ghost.js.map +1 -0
  15. package/dist/commands/info.d.ts +4 -0
  16. package/dist/commands/info.d.ts.map +1 -0
  17. package/dist/commands/info.js +23 -0
  18. package/dist/commands/info.js.map +1 -0
  19. package/dist/commands/list.d.ts +3 -0
  20. package/dist/commands/list.d.ts.map +1 -0
  21. package/dist/commands/list.js +27 -0
  22. package/dist/commands/list.js.map +1 -0
  23. package/dist/commands/log.d.ts +3 -0
  24. package/dist/commands/log.d.ts.map +1 -0
  25. package/dist/commands/log.js +62 -0
  26. package/dist/commands/log.js.map +1 -0
  27. package/dist/commands/purge.d.ts +9 -0
  28. package/dist/commands/purge.d.ts.map +1 -0
  29. package/dist/commands/purge.js +62 -0
  30. package/dist/commands/purge.js.map +1 -0
  31. package/dist/commands/resync.d.ts +8 -0
  32. package/dist/commands/resync.d.ts.map +1 -0
  33. package/dist/commands/resync.js +218 -0
  34. package/dist/commands/resync.js.map +1 -0
  35. package/dist/commands/source.d.ts +3 -0
  36. package/dist/commands/source.d.ts.map +1 -0
  37. package/dist/commands/source.js +80 -0
  38. package/dist/commands/source.js.map +1 -0
  39. package/dist/commands/sync.d.ts +14 -0
  40. package/dist/commands/sync.d.ts.map +1 -0
  41. package/dist/commands/sync.js +288 -0
  42. package/dist/commands/sync.js.map +1 -0
  43. package/dist/config.d.ts +29 -0
  44. package/dist/config.d.ts.map +1 -0
  45. package/dist/config.js +164 -0
  46. package/dist/config.js.map +1 -0
  47. package/dist/glob.d.ts +2 -0
  48. package/dist/glob.d.ts.map +1 -0
  49. package/dist/glob.js +5 -0
  50. package/dist/glob.js.map +1 -0
  51. package/dist/scopy.d.ts +3 -0
  52. package/dist/scopy.d.ts.map +1 -0
  53. package/dist/scopy.js +37 -0
  54. package/dist/scopy.js.map +1 -0
  55. package/dist/types.d.ts +47 -0
  56. package/dist/types.d.ts.map +1 -0
  57. package/dist/types.js +7 -0
  58. package/dist/types.js.map +1 -0
  59. package/dist/ui.d.ts +12 -0
  60. package/dist/ui.d.ts.map +1 -0
  61. package/dist/ui.js +37 -0
  62. package/dist/ui.js.map +1 -0
  63. package/dist/validate.d.ts +6 -0
  64. package/dist/validate.d.ts.map +1 -0
  65. package/dist/validate.js +13 -0
  66. package/dist/validate.js.map +1 -0
  67. 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
+ [![npm version](https://img.shields.io/npm/v/@koder-0x/scopy)](https://www.npmjs.com/package/@koder-0x/scopy)
4
+ [![license](https://img.shields.io/npm/l/@koder-0x/scopy)](LICENSE)
5
+ [![node](https://img.shields.io/node/v/@koder-0x/scopy)](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
+ ![Demo](docs/demo.gif)
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,3 @@
1
+ import type { Command } from 'commander';
2
+ export default function registerConfig(program: Command): void;
3
+ //# sourceMappingURL=config.d.ts.map
@@ -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,3 @@
1
+ import type { Command } from 'commander';
2
+ export default function register(program: Command): void;
3
+ //# sourceMappingURL=dest.d.ts.map
@@ -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,4 @@
1
+ import type { Command } from 'commander';
2
+ export declare function handleGhost(dest: string | undefined, selector: string | undefined): Promise<void>;
3
+ export default function register(program: Command): void;
4
+ //# sourceMappingURL=ghost.d.ts.map
@@ -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":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,QAAQ,EAAE,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,sBAAsB,EACtB,UAAU,EACV,aAAa,EACb,OAAO,GACR,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,KAAK,IAAI,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGzC,SAAS,YAAY,CAAC,IAAY,EAAE,YAAoB,EAAE,MAAkB;IAC1E,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAC/B,OAAO,CAAC,eAAe,MAAM,CAAC,IAAI,2BAA2B,CAAC,CAAC;QAC/D,OAAO;IACT,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;IAEtD,IAAI,MAAM,CAAC,OAAO,KAAK,KAAK,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QAC7D,MAAM,SAAS,GAAG,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;QACpD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC9B,GAAG,CAAC,wBAAwB,MAAM,CAAC,IAAI,qCAAqC,CAAC,CAAC;QAChF,CAAC;QAED,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACrC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACrC,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,MAAM,CAAC,IAAI,uBAAuB,CAAC,CAAC;IAC7E,CAAC;SAAM,CAAC;QACN,MAAM,SAAS,GAAG,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;QACpD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC9B,OAAO,CAAC,wBAAwB,MAAM,CAAC,IAAI,oBAAoB,CAAC,CAAC;YACjE,OAAO;QACT,CAAC;QAED,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1D,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QACrC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACtC,OAAO,CAAC;YACN,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,WAAW,EAAE,IAAI;YACjB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACnC,CAAC,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,MAAM,CAAC,IAAI,wBAAwB,CAAC,CAAC;IAClF,CAAC;AACH,CAAC;AAED,KAAK,UAAU,sBAAsB,CAAC,UAAmB;IACvD,MAAM,YAAY,GAAG,UAAU,KAAK,SAAS;QAC3C,CAAC,CAAC,eAAe,EAAE;QACnB,CAAC,CAAC,eAAe,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;IAC3D,MAAM,SAAS,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,sBAAsB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAExF,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,GAAG,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,oCAAoC,CAAC,CAAC,CAAC,qCAAqC,UAAU,GAAG,CAAC,CAAC;QAC1H,OAAO;IACT,CAAC;IAID,MAAM,OAAO,GAAmG,EAAE,CAAC;IAEnH,MAAM,UAAU,GAA2E,IAAI,GAAG,EAAE,CAAC;IAErG,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;QAC1B,MAAM,OAAO,GAAG,sBAAsB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC/C,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;QACxE,OAAO,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAChD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;gBACrC,OAAO,EAAE,CAAC,CAAC,OAAO,KAAK,IAAI;aAC5B,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC;QAC5B,OAAO,EAAE,EAAE;QACX,OAAO;QACP,IAAI,EAAE,KAAK;QACX,KAAK,EAAE;YACL,IAAI,EAAE;gBACJ,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC;gBACpC,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,gBAAgB,CAAC;gBACxC,MAAM,EAAE,GAAG;aACZ;YACD,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;YAC9B,KAAK,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE;SAC/C;KACF,CAAC,CAAC;IAEH,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAEtE,KAAK,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC;QAC9D,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,MAAM,GAAG,GAAG,GAAG,IAAI,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC;YACtC,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,KAAK,IAAI,CAAC;YAC3C,MAAM,eAAe,GAAG,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC5C,IAAI,UAAU,KAAK,eAAe,EAAE,CAAC;gBACnC,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;YACvC,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAwB,EAAE,QAA4B;IACtF,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,sBAAsB,EAAE,CAAC;QAC/B,OAAO;IACT,CAAC;IAED,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,gBAAgB,IAAI,qBAAqB,CAAC,CAAC;QACnD,OAAO;IACT,CAAC;IAED,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,MAAM,sBAAsB,CAAC,IAAI,CAAC,CAAC;QACnC,OAAO;IACT,CAAC;IAED,MAAM,YAAY,GAAG,eAAe,EAAE,CAAC;IACvC,MAAM,WAAW,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAC9D,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,CAAC,gCAAgC,IAAI,8BAA8B,CAAC,CAAC;QAC5E,OAAO;IACT,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IACrC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QAClB,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC3C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,CAAC,sBAAsB,KAAK,qBAAqB,IAAI,GAAG,CAAC,CAAC;YACjE,OAAO;QACT,CAAC;QACD,YAAY,CAAC,IAAI,EAAE,WAAW,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACnD,CAAC;SAAM,CAAC;QACN,MAAM,KAAK,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;QACpC,MAAM,OAAO,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QAC/E,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,GAAG,CAAC,0BAA0B,QAAQ,GAAG,CAAC,CAAC;YAC3C,OAAO;QACT,CAAC;QACD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,YAAY,CAAC,IAAI,EAAE,WAAW,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,OAAgB;IAC/C,OAAO;SACJ,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,oFAAoF,CAAC;SACjG,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;SACtC,QAAQ,CAAC,YAAY,EAAE,yDAAyD,CAAC;SACjF,MAAM,CAAC,WAAW,CAAC,CAAC;AACzB,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { Command } from 'commander';
2
+ import type { PackageJson } from '../types.js';
3
+ export default function register(program: Command, pkg: PackageJson): void;
4
+ //# sourceMappingURL=info.d.ts.map
@@ -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,3 @@
1
+ import type { Command } from 'commander';
2
+ export default function register(program: Command): void;
3
+ //# sourceMappingURL=list.d.ts.map
@@ -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,3 @@
1
+ import type { Command } from 'commander';
2
+ export default function register(program: Command): void;
3
+ //# sourceMappingURL=log.d.ts.map
@@ -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":"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"}
@@ -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