@firenet-designs/fnd-cli 2.3.2 → 2.3.3

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 CHANGED
@@ -20,7 +20,7 @@ $ npm install -g @firenet-designs/fnd-cli
20
20
  $ fnd COMMAND
21
21
  running command...
22
22
  $ fnd (--version)
23
- @firenet-designs/fnd-cli/2.3.2 linux-x64 node-v24.18.0
23
+ @firenet-designs/fnd-cli/2.3.3 linux-x64 node-v24.18.0
24
24
  $ fnd --help [COMMAND]
25
25
  USAGE
26
26
  $ fnd COMMAND
@@ -62,7 +62,7 @@ EXAMPLES
62
62
  $ fnd backfill-project my-store "extra hints"
63
63
  ```
64
64
 
65
- _See code: [src/commands/backfill-project.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.3.2/src/commands/backfill-project.ts)_
65
+ _See code: [src/commands/backfill-project.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.3.3/src/commands/backfill-project.ts)_
66
66
 
67
67
  ## `fnd create-app`
68
68
 
@@ -83,7 +83,7 @@ EXAMPLES
83
83
  $ fnd create-app
84
84
  ```
85
85
 
86
- _See code: [src/commands/create-app.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.3.2/src/commands/create-app.ts)_
86
+ _See code: [src/commands/create-app.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.3.3/src/commands/create-app.ts)_
87
87
 
88
88
  ## `fnd create-project [SHOP]`
89
89
 
@@ -113,7 +113,7 @@ EXAMPLES
113
113
  $ fnd create-project my-store "Acme storefront, Klaviyo"
114
114
  ```
115
115
 
116
- _See code: [src/commands/create-project.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.3.2/src/commands/create-project.ts)_
116
+ _See code: [src/commands/create-project.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.3.3/src/commands/create-project.ts)_
117
117
 
118
118
  ## `fnd help [COMMAND]`
119
119
 
@@ -165,7 +165,7 @@ EXAMPLES
165
165
  $ fnd token -s mystore -i <id> -c <secret> --write ./.mcp.json
166
166
  ```
167
167
 
168
- _See code: [src/commands/token.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.3.2/src/commands/token.ts)_
168
+ _See code: [src/commands/token.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.3.3/src/commands/token.ts)_
169
169
 
170
170
  ## `fnd workspace`
171
171
 
@@ -173,9 +173,11 @@ Open a remote workspace: two-way sync the current directory to a remote Linux bo
173
173
 
174
174
  ```
175
175
  USAGE
176
- $ fnd workspace --ssh <value> [--devtools <value>] [--remote-base <value>] [--source remote|local]
176
+ $ fnd workspace --ssh <value> [--delete-remote-dir] [--devtools <value>] [--remote-base <value>] [--source
177
+ remote|local]
177
178
 
178
179
  FLAGS
180
+ --delete-remote-dir on exit, delete the remote workspace directory instead of leaving the synced copy in place
179
181
  --devtools=<value> expose your LOCAL browser to Claude on the remote via the chrome-devtools MCP. Value is "port"
180
182
  (same port both ends) or "remote:local" (local = this machine, where the browser runs). Your
181
183
  browser must already be listening with --remote-debugging-port=<local>.
@@ -206,9 +208,11 @@ EXAMPLES
206
208
  $ fnd workspace --ssh user@host --devtools 9222
207
209
 
208
210
  $ fnd workspace --ssh user@host --devtools 9333:9222
211
+
212
+ $ fnd workspace --ssh user@host --delete-remote-dir
209
213
  ```
210
214
 
211
- _See code: [src/commands/workspace/index.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.3.2/src/commands/workspace/index.ts)_
215
+ _See code: [src/commands/workspace/index.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.3.3/src/commands/workspace/index.ts)_
212
216
 
213
217
  ## `fnd workspace cleanup`
214
218
 
@@ -216,9 +220,13 @@ Tear down a leftover workspace — use this if a `workspace` session dropped bef
216
220
 
217
221
  ```
218
222
  USAGE
219
- $ fnd workspace cleanup --ssh <value> [--remote-base <value>] [--remote-dir <value>]
223
+ $ fnd workspace cleanup --ssh <value> [--delete-remote-dir] [--devtools] [--remote-base <value>] [--remote-dir
224
+ <value>]
220
225
 
221
226
  FLAGS
227
+ --delete-remote-dir also delete the remote workspace directory (by default the synced files are left in place)
228
+ --devtools also strip the chrome-devtools MCP entry from the remote (only if the dropped session used
229
+ --devtools)
222
230
  --remote-base=<value> [default: /home/fnd] base dir on the remote, used to derive the default remote directory path
223
231
  --remote-dir=<value> exact remote directory to target (defaults to the current dir mapping)
224
232
  --ssh=<value> (required) remote to connect to, as user@host
@@ -226,15 +234,20 @@ FLAGS
226
234
  DESCRIPTION
227
235
  Tear down a leftover workspace — use this if a `workspace` session dropped before it could clean up after itself.
228
236
 
229
- Terminates any Mutagen sync sessions this machine started for the directory and strips the matching chrome-devtools
230
- MCP entry from the remote. With no --remote-dir, it targets the same path `workspace` would use for the current
231
- directory. The synced files themselves are left in place.
237
+ Terminates any Mutagen sync sessions this machine started for the directory. Pass --devtools to also strip the
238
+ matching chrome-devtools MCP entry from the remote (only do this if the dropped session used --devtools). With no
239
+ --remote-dir, it targets the same path `workspace` would use for the current directory. The synced files themselves
240
+ are left in place unless you pass --delete-remote-dir.
232
241
 
233
242
  EXAMPLES
234
243
  $ fnd workspace cleanup --ssh user@host
235
244
 
236
245
  $ fnd workspace cleanup --ssh user@host --remote-dir /home/fnd/cole/fnd-cli
246
+
247
+ $ fnd workspace cleanup --ssh user@host --devtools
248
+
249
+ $ fnd workspace cleanup --ssh user@host --delete-remote-dir
237
250
  ```
238
251
 
239
- _See code: [src/commands/workspace/cleanup.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.3.2/src/commands/workspace/cleanup.ts)_
252
+ _See code: [src/commands/workspace/cleanup.ts](https://github.com/FireNet-Designs/fnd-cli/blob/v2.3.3/src/commands/workspace/cleanup.ts)_
240
253
  <!-- commandsstop -->
@@ -3,6 +3,8 @@ export default class WorkspaceCleanup extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static flags: {
6
+ 'delete-remote-dir': import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ devtools: import("@oclif/core/interfaces").BooleanFlag<boolean>;
6
8
  'remote-base': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
9
  'remote-dir': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
10
  ssh: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
@@ -3,12 +3,22 @@ import chalk from 'chalk';
3
3
  import { basename } from 'node:path';
4
4
  import { buildContext, buildMutagenTerminateSelectorArgs, DEFAULT_MOUNT_BASE, hasMutagen, hasSshClient, parseSshTarget, runMutagen, runRemoteCleanup, slugify, } from '../../lib/workspace.js';
5
5
  export default class WorkspaceCleanup extends Command {
6
- static description = 'Tear down a leftover workspace — use this if a `workspace` session dropped before it could clean up after itself.\n\nTerminates any Mutagen sync sessions this machine started for the directory and strips the matching chrome-devtools MCP entry from the remote. With no --remote-dir, it targets the same path `workspace` would use for the current directory. The synced files themselves are left in place.';
6
+ static description = 'Tear down a leftover workspace — use this if a `workspace` session dropped before it could clean up after itself.\n\nTerminates any Mutagen sync sessions this machine started for the directory. Pass --devtools to also strip the matching chrome-devtools MCP entry from the remote (only do this if the dropped session used --devtools). With no --remote-dir, it targets the same path `workspace` would use for the current directory. The synced files themselves are left in place unless you pass --delete-remote-dir.';
7
7
  static examples = [
8
8
  '<%= config.bin %> <%= command.id %> --ssh user@host',
9
9
  '<%= config.bin %> <%= command.id %> --ssh user@host --remote-dir /home/fnd/cole/fnd-cli',
10
+ '<%= config.bin %> <%= command.id %> --ssh user@host --devtools',
11
+ '<%= config.bin %> <%= command.id %> --ssh user@host --delete-remote-dir',
10
12
  ];
11
13
  static flags = {
14
+ 'delete-remote-dir': Flags.boolean({
15
+ default: false,
16
+ description: 'also delete the remote workspace directory (by default the synced files are left in place)',
17
+ }),
18
+ devtools: Flags.boolean({
19
+ default: false,
20
+ description: 'also strip the chrome-devtools MCP entry from the remote (only if the dropped session used --devtools)',
21
+ }),
12
22
  'remote-base': Flags.string({
13
23
  default: DEFAULT_MOUNT_BASE,
14
24
  description: 'base dir on the remote, used to derive the default remote directory path',
@@ -43,9 +53,18 @@ export default class WorkspaceCleanup extends Command {
43
53
  else {
44
54
  this.log(chalk.yellow('Mutagen CLI not found on PATH — skipping sync termination.'));
45
55
  }
46
- // Strip any chrome-devtools MCP config this workspace left on the remote.
47
- this.log(chalk.dim('Removing any leftover chrome-devtools MCP config on the remote…'));
48
- const code = await runRemoteCleanup(target2, remoteDir);
56
+ // Strip any chrome-devtools MCP config this workspace left on the remote
57
+ // (only when asked), and optionally delete the synced directory itself.
58
+ if (flags.devtools) {
59
+ this.log(chalk.dim('Removing any leftover chrome-devtools MCP config on the remote…'));
60
+ }
61
+ if (flags['delete-remote-dir']) {
62
+ this.log(chalk.dim('Deleting the remote workspace directory…'));
63
+ }
64
+ const code = await runRemoteCleanup(target2, remoteDir, {
65
+ deleteRemoteDir: flags['delete-remote-dir'],
66
+ removeDevtoolsMcp: flags.devtools,
67
+ });
49
68
  if (code === 0) {
50
69
  this.log(chalk.green('✓ Done.'));
51
70
  }
@@ -3,6 +3,7 @@ export default class Workspace extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static flags: {
6
+ 'delete-remote-dir': import("@oclif/core/interfaces").BooleanFlag<boolean>;
6
7
  devtools: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
8
  'remote-base': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
8
9
  source: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -10,8 +10,13 @@ export default class Workspace extends Command {
10
10
  '<%= config.bin %> <%= command.id %> --ssh user@host --remote-base /home/fnd',
11
11
  '<%= config.bin %> <%= command.id %> --ssh user@host --devtools 9222',
12
12
  '<%= config.bin %> <%= command.id %> --ssh user@host --devtools 9333:9222',
13
+ '<%= config.bin %> <%= command.id %> --ssh user@host --delete-remote-dir',
13
14
  ];
14
15
  static flags = {
16
+ 'delete-remote-dir': Flags.boolean({
17
+ default: false,
18
+ description: 'on exit, delete the remote workspace directory instead of leaving the synced copy in place',
19
+ }),
15
20
  devtools: Flags.string({
16
21
  description: 'expose your LOCAL browser to Claude on the remote via the chrome-devtools MCP. Value is "port" (same port both ends) or "remote:local" (local = this machine, where the browser runs). Your browser must already be listening with --remote-debugging-port=<local>.',
17
22
  }),
@@ -47,6 +52,7 @@ export default class Workspace extends Command {
47
52
  if (createCode !== 0) {
48
53
  this.error(`mutagen sync create failed (exit ${createCode}). Check that the remote is reachable over SSH and try again.`, { code: '1' });
49
54
  }
55
+ const deleteRemoteDir = flags['delete-remote-dir'];
50
56
  let code;
51
57
  try {
52
58
  // Block until the first full sync lands so the files exist before the shell opens.
@@ -64,20 +70,30 @@ export default class Workspace extends Command {
64
70
  this.log(chalk.dim('Flushing final changes and stopping the sync…'));
65
71
  await runMutagen(buildMutagenFlushArgs(ctx.syncName)).catch(() => 1);
66
72
  await runMutagen(buildMutagenTerminateArgs(ctx.syncName)).catch(() => 1);
67
- // Only reach back to the remote if we put a devtools MCP config there to remove.
68
- if (ctx.devtools) {
69
- this.log(chalk.dim('Removing the remote chrome-devtools MCP config…'));
73
+ // Reach back to the remote if we left a devtools MCP config there to remove,
74
+ // or if we were asked to delete the synced directory.
75
+ if (ctx.devtools || deleteRemoteDir) {
76
+ const actions = [
77
+ ctx.devtools ? 'removing the remote chrome-devtools MCP config' : undefined,
78
+ deleteRemoteDir ? 'deleting the remote dir' : undefined,
79
+ ].filter(Boolean);
80
+ this.log(chalk.dim(`${actions.join(' and ')}…`.replace(/^./, (c) => c.toUpperCase())));
70
81
  try {
71
- await runRemoteCleanup(target2, ctx.remoteDir);
82
+ await runRemoteCleanup(target2, ctx.remoteDir, {
83
+ deleteRemoteDir,
84
+ removeDevtoolsMcp: Boolean(ctx.devtools),
85
+ });
72
86
  }
73
87
  catch (error) {
74
- this.log(chalk.yellow(`Could not reach the remote to remove the devtools config (${error.message}).`));
88
+ this.log(chalk.yellow(`Could not reach the remote to clean up (${error.message}).`));
75
89
  }
76
90
  }
77
91
  }
78
92
  this.log('');
79
93
  this.log(code === 0
80
- ? chalk.green('✓ Workspace closed. Sync stopped; the remote copy is left in place.')
94
+ ? chalk.green(deleteRemoteDir
95
+ ? '✓ Workspace closed. Sync stopped; the remote dir was deleted.'
96
+ : '✓ Workspace closed. Sync stopped; the remote copy is left in place.')
81
97
  : chalk.yellow(`Session ended with exit code ${code}. Cleanup attempted above.`));
82
98
  }
83
99
  /** Verify this machine can drive the sync before we connect. */
@@ -92,15 +92,25 @@ export declare const shQuote: (value: string) => string;
92
92
  * optionally wires up the chrome-devtools MCP, and drops into a login shell.
93
93
  */
94
94
  export declare const buildRemoteScript: (ctx: WorkspaceContext) => string;
95
- /** Run the remote-side teardown (strip this project's chrome-devtools MCP) over a fresh ssh connection. */
96
- export declare const runRemoteCleanup: (target: string, remoteDir: string) => Promise<number>;
95
+ /** Options controlling what the remote-side teardown does. */
96
+ export interface RemoteCleanupOptions {
97
+ /** Delete the workspace dir after cleanup; by default the synced copy is left in place. */
98
+ deleteRemoteDir?: boolean;
99
+ /** Strip this project's chrome-devtools MCP entry — only when the workspace registered one (--devtools). */
100
+ removeDevtoolsMcp?: boolean;
101
+ }
102
+ /** Run the remote-side teardown over a fresh ssh connection. */
103
+ export declare const runRemoteCleanup: (target: string, remoteDir: string, opts?: RemoteCleanupOptions) => Promise<number>;
97
104
  /**
98
- * The remote-side teardown script: strip this project's chrome-devtools MCP via
99
- * the `claude` CLI. Runs from inside the workspace dir so local scope resolves to
100
- * the right project. A no-op if there is nothing to undo. The synced files
101
- * themselves are left in place they are a real copy, not a mount.
105
+ * The remote-side teardown script. Runs from inside the workspace dir so the
106
+ * `claude` CLI's local scope resolves to the right project. The chrome-devtools
107
+ * MCP entry is stripped only when `removeDevtoolsMcp` is set i.e. when the
108
+ * workspace registered one via --devtools; without it we never touch the user's
109
+ * MCP config. The synced files themselves are left in place — they are a real
110
+ * copy, not a mount — unless `deleteRemoteDir` is set, in which case the
111
+ * workspace dir is removed after any MCP config is stripped.
102
112
  */
103
- export declare const buildCleanupScript: (remoteDir: string) => string;
113
+ export declare const buildCleanupScript: (remoteDir: string, opts?: RemoteCleanupOptions) => string;
104
114
  /** True if an `ssh` client is on PATH (works on Windows, macOS, Linux). */
105
115
  export declare const hasSshClient: () => boolean;
106
116
  /** True if the Mutagen CLI is on PATH and runnable. */
@@ -124,22 +124,33 @@ export const buildRemoteScript = (ctx) => {
124
124
  '"${SHELL:-bash}" -l',
125
125
  ].join('\n');
126
126
  };
127
- /** Run the remote-side teardown (strip this project's chrome-devtools MCP) over a fresh ssh connection. */
128
- export const runRemoteCleanup = (target, remoteDir) => new Promise((resolve, reject) => {
129
- const child = spawn('ssh', [target, buildCleanupScript(remoteDir)], { stdio: 'inherit' });
127
+ /** Run the remote-side teardown over a fresh ssh connection. */
128
+ export const runRemoteCleanup = (target, remoteDir, opts = {}) => new Promise((resolve, reject) => {
129
+ const child = spawn('ssh', [target, buildCleanupScript(remoteDir, opts)], { stdio: 'inherit' });
130
130
  child.once('error', reject);
131
131
  child.once('close', (code) => resolve(code ?? 0));
132
132
  });
133
133
  /**
134
- * The remote-side teardown script: strip this project's chrome-devtools MCP via
135
- * the `claude` CLI. Runs from inside the workspace dir so local scope resolves to
136
- * the right project. A no-op if there is nothing to undo. The synced files
137
- * themselves are left in place they are a real copy, not a mount.
134
+ * The remote-side teardown script. Runs from inside the workspace dir so the
135
+ * `claude` CLI's local scope resolves to the right project. The chrome-devtools
136
+ * MCP entry is stripped only when `removeDevtoolsMcp` is set i.e. when the
137
+ * workspace registered one via --devtools; without it we never touch the user's
138
+ * MCP config. The synced files themselves are left in place — they are a real
139
+ * copy, not a mount — unless `deleteRemoteDir` is set, in which case the
140
+ * workspace dir is removed after any MCP config is stripped.
138
141
  */
139
- export const buildCleanupScript = (remoteDir) => [
142
+ export const buildCleanupScript = (remoteDir, opts = {}) => [
140
143
  `DIR=${shQuote(remoteDir)}`,
141
144
  'cd "$DIR" 2>/dev/null || { echo "Nothing to clean up: $DIR is gone." >&2; exit 0; }',
142
- ...claudeDevtoolsRemoveScript(),
145
+ ...(opts.removeDevtoolsMcp ? claudeDevtoolsRemoveScript() : []),
146
+ // Remove the workspace dir last: cd out first so we don't rm the cwd out from
147
+ // under the shell, then delete it. Only when explicitly requested.
148
+ ...(opts.deleteRemoteDir
149
+ ? [
150
+ 'cd / || exit 1',
151
+ 'rm -rf "$DIR" && echo "Removed remote dir $DIR." || { echo "ERROR: could not remove $DIR" >&2; exit 1; }',
152
+ ]
153
+ : []),
143
154
  ].join('\n');
144
155
  /** True if an `ssh` client is on PATH (works on Windows, macOS, Linux). */
145
156
  export const hasSshClient = () => {
@@ -186,12 +186,26 @@
186
186
  "workspace:cleanup": {
187
187
  "aliases": [],
188
188
  "args": {},
189
- "description": "Tear down a leftover workspace — use this if a `workspace` session dropped before it could clean up after itself.\n\nTerminates any Mutagen sync sessions this machine started for the directory and strips the matching chrome-devtools MCP entry from the remote. With no --remote-dir, it targets the same path `workspace` would use for the current directory. The synced files themselves are left in place.",
189
+ "description": "Tear down a leftover workspace — use this if a `workspace` session dropped before it could clean up after itself.\n\nTerminates any Mutagen sync sessions this machine started for the directory. Pass --devtools to also strip the matching chrome-devtools MCP entry from the remote (only do this if the dropped session used --devtools). With no --remote-dir, it targets the same path `workspace` would use for the current directory. The synced files themselves are left in place unless you pass --delete-remote-dir.",
190
190
  "examples": [
191
191
  "<%= config.bin %> <%= command.id %> --ssh user@host",
192
- "<%= config.bin %> <%= command.id %> --ssh user@host --remote-dir /home/fnd/cole/fnd-cli"
192
+ "<%= config.bin %> <%= command.id %> --ssh user@host --remote-dir /home/fnd/cole/fnd-cli",
193
+ "<%= config.bin %> <%= command.id %> --ssh user@host --devtools",
194
+ "<%= config.bin %> <%= command.id %> --ssh user@host --delete-remote-dir"
193
195
  ],
194
196
  "flags": {
197
+ "delete-remote-dir": {
198
+ "description": "also delete the remote workspace directory (by default the synced files are left in place)",
199
+ "name": "delete-remote-dir",
200
+ "allowNo": false,
201
+ "type": "boolean"
202
+ },
203
+ "devtools": {
204
+ "description": "also strip the chrome-devtools MCP entry from the remote (only if the dropped session used --devtools)",
205
+ "name": "devtools",
206
+ "allowNo": false,
207
+ "type": "boolean"
208
+ },
195
209
  "remote-base": {
196
210
  "description": "base dir on the remote, used to derive the default remote directory path",
197
211
  "name": "remote-base",
@@ -241,9 +255,16 @@
241
255
  "<%= config.bin %> <%= command.id %> --ssh user@host --source local",
242
256
  "<%= config.bin %> <%= command.id %> --ssh user@host --remote-base /home/fnd",
243
257
  "<%= config.bin %> <%= command.id %> --ssh user@host --devtools 9222",
244
- "<%= config.bin %> <%= command.id %> --ssh user@host --devtools 9333:9222"
258
+ "<%= config.bin %> <%= command.id %> --ssh user@host --devtools 9333:9222",
259
+ "<%= config.bin %> <%= command.id %> --ssh user@host --delete-remote-dir"
245
260
  ],
246
261
  "flags": {
262
+ "delete-remote-dir": {
263
+ "description": "on exit, delete the remote workspace directory instead of leaving the synced copy in place",
264
+ "name": "delete-remote-dir",
265
+ "allowNo": false,
266
+ "type": "boolean"
267
+ },
247
268
  "devtools": {
248
269
  "description": "expose your LOCAL browser to Claude on the remote via the chrome-devtools MCP. Value is \"port\" (same port both ends) or \"remote:local\" (local = this machine, where the browser runs). Your browser must already be listening with --remote-debugging-port=<local>.",
249
270
  "name": "devtools",
@@ -296,5 +317,5 @@
296
317
  ]
297
318
  }
298
319
  },
299
- "version": "2.3.2"
320
+ "version": "2.3.3"
300
321
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@firenet-designs/fnd-cli",
3
3
  "description": "A new CLI generated with oclif",
4
- "version": "2.3.2",
4
+ "version": "2.3.3",
5
5
  "author": "Cole Denslow",
6
6
  "contributors": [
7
7
  "Justin Schellenberg"