@moglenny/content-workbench-agent 0.1.2 → 0.1.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/bin.mjs +52 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moglenny/content-workbench-agent",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Agent installer for Content Workbench CLI, Skills, and MCP configuration",
5
5
  "type": "module",
6
6
  "bin": {
package/src/bin.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { spawn } from "node:child_process";
4
4
  import { realpathSync } from "node:fs";
5
- import { mkdir, rm, stat } from "node:fs/promises";
5
+ import { mkdir, rename, rm, stat } from "node:fs/promises";
6
6
  import os from "node:os";
7
7
  import path from "node:path";
8
8
  import { fileURLToPath } from "node:url";
@@ -40,9 +40,12 @@ Options:
40
40
  --repo-url <url> Git repository URL. Defaults to the official content workbench repo.
41
41
  --branch <name> Branch to install from. Defaults to main.
42
42
  --repo-dir <path> Local managed clone path. Defaults to ~/.content-workbench/agent/topic-dashboard.
43
+ --cli-prefix <path> User-owned npm prefix for cw. Defaults to ~/.content-workbench/npm.
43
44
  --skills-target <path> Skill install target. Repeatable.
44
45
  --mcp-target <path> Write MCP config snippet to this JSON file.
45
- --skip-cli Do not install the global cw CLI.
46
+ --shell-profile <path> Shell profile that should include cw in PATH.
47
+ --skip-cli Do not install the user-level cw CLI.
48
+ --skip-shell-profile Do not add cw to the shell profile PATH.
46
49
  --skip-skills Do not copy Skills.
47
50
  --skip-mcp Do not write MCP config.
48
51
  --api-base-url <url> Base URL shown in post-install setup guidance.
@@ -92,13 +95,13 @@ export function parseAgentInstallerArgs(argv = process.argv.slice(2)) {
92
95
  options.repoDir = path.resolve(expandHome(args[++index] || ""));
93
96
  continue;
94
97
  }
95
- if (arg === "--skills-target" || arg === "--mcp-target" || arg === "--api-base-url") {
98
+ if (arg === "--skills-target" || arg === "--mcp-target" || arg === "--api-base-url" || arg === "--cli-prefix" || arg === "--shell-profile") {
96
99
  const value = args[++index];
97
100
  if (!value) throw new Error(`Missing value for ${arg}`);
98
101
  options.installArgs.push(arg, value);
99
102
  continue;
100
103
  }
101
- if (arg === "--skip-cli" || arg === "--skip-skills" || arg === "--skip-mcp") {
104
+ if (arg === "--skip-cli" || arg === "--skip-shell-profile" || arg === "--skip-skills" || arg === "--skip-mcp") {
102
105
  options.installArgs.push(arg);
103
106
  continue;
104
107
  }
@@ -217,6 +220,24 @@ async function runStepWithRetry(step, options) {
217
220
  }
218
221
  }
219
222
 
223
+ async function runSteps(steps, options) {
224
+ for (const step of steps) {
225
+ await runStepWithRetry(step, options);
226
+ }
227
+ }
228
+
229
+ async function clearManagedRepo(options) {
230
+ try {
231
+ await options.removeDir(options.repoDir, { recursive: true, force: true });
232
+ return null;
233
+ } catch {
234
+ const quarantineDir = `${options.repoDir}.broken-${options.now()}`;
235
+ await options.renameDir(options.repoDir, quarantineDir);
236
+ options.stdout.write(`Moved inaccessible managed repository to ${quarantineDir}\n`);
237
+ return quarantineDir;
238
+ }
239
+ }
240
+
220
241
  function printPlan(plan, stdout) {
221
242
  stdout.write(`${plan.dryRun ? "Content Workbench Agent install dry run" : "Content Workbench Agent install"}\n`);
222
243
  stdout.write(`Managed repository: ${plan.repoDir}\n`);
@@ -244,15 +265,34 @@ export async function runAgentInstaller(argv = process.argv.slice(2), io = proce
244
265
 
245
266
  const runner = io.run || run;
246
267
  const removeDir = io.removeDir || rm;
268
+ const renameDir = io.renameDir || rename;
247
269
  const wait = io.sleep || sleep;
248
- for (const step of plan.steps) {
249
- await runStepWithRetry(step, {
250
- runner,
251
- removeDir,
252
- repoDir: options.repoDir,
253
- sleep: wait,
254
- stdout: io.stdout,
255
- });
270
+ const runnerOptions = {
271
+ runner,
272
+ removeDir,
273
+ renameDir,
274
+ repoDir: options.repoDir,
275
+ sleep: wait,
276
+ now: io.now || Date.now,
277
+ stdout: io.stdout,
278
+ };
279
+
280
+ if (repoExists) {
281
+ const updateSteps = plan.steps.slice(0, 3);
282
+ const installStep = plan.steps.at(-1);
283
+ try {
284
+ await runSteps(updateSteps, runnerOptions);
285
+ } catch {
286
+ io.stdout.write("Existing managed repository could not be updated. Recreating a clean managed repository...\n");
287
+ const quarantinedRepoDir = await clearManagedRepo(runnerOptions);
288
+ const cleanPlan = buildInstallPlan(options, false);
289
+ await runSteps(cleanPlan.steps, runnerOptions);
290
+ io.stdout.write("Content Workbench Agent install complete\n");
291
+ return { ...cleanPlan, recreatedExistingRepo: true, quarantinedRepoDir };
292
+ }
293
+ await runSteps([installStep], runnerOptions);
294
+ } else {
295
+ await runSteps(plan.steps, runnerOptions);
256
296
  }
257
297
 
258
298
  io.stdout.write("Content Workbench Agent install complete\n");