@uluops/setup 0.4.0 → 0.6.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 (213) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +75 -60
  3. package/assets/auto-tracker-save.mjs +142 -0
  4. package/assets/{agents → claude-code/agents}/api-contract-validator-agent.md +9 -228
  5. package/assets/{agents → claude-code/agents}/aristotle-analyst-agent.md +51 -4
  6. package/assets/{agents → claude-code/agents}/aristotle-explorer-agent.md +6 -2
  7. package/assets/{agents → claude-code/agents}/aristotle-forecaster-agent.md +15 -230
  8. package/assets/{agents → claude-code/agents}/aristotle-validator-agent.md +12 -252
  9. package/assets/{agents → claude-code/agents}/assumption-excavator-agent.md +21 -247
  10. package/assets/{agents → claude-code/agents}/code-auditor-agent.md +12 -255
  11. package/assets/{agents → claude-code/agents}/code-optimizer-agent.md +15 -236
  12. package/assets/{agents → claude-code/agents}/code-validator-agent.md +31 -300
  13. package/assets/claude-code/agents/docs-validator-agent.md +472 -0
  14. package/assets/{agents → claude-code/agents}/frontend-validator-agent.md +15 -258
  15. package/assets/{agents → claude-code/agents}/mcp-validator-agent.md +8 -252
  16. package/assets/{agents → claude-code/agents}/pre-implementation-architect-agent.md +8 -224
  17. package/assets/{agents → claude-code/agents}/prompt-engineer-agent.md +57 -290
  18. package/assets/{agents → claude-code/agents}/prompt-pattern-analyzer-agent.md +10 -225
  19. package/assets/{agents → claude-code/agents}/prompt-quality-validator-agent.md +11 -249
  20. package/assets/{agents → claude-code/agents}/public-interface-validator-agent.md +15 -268
  21. package/assets/claude-code/agents/release-readiness-agent.md +495 -0
  22. package/assets/{agents → claude-code/agents}/security-analyst-agent.md +236 -480
  23. package/assets/{agents → claude-code/agents}/test-architect-agent.md +16 -259
  24. package/assets/{agents → claude-code/agents}/type-safety-validator-agent.md +23 -266
  25. package/assets/{agents → claude-code/agents}/workflow-synthesis-agent.md +23 -226
  26. package/assets/{commands → claude-code/commands}/agents/anxiety-reader.md +12 -15
  27. package/assets/{commands → claude-code/commands}/agents/api-contract.md +156 -136
  28. package/assets/{commands → claude-code/commands}/agents/architect.md +156 -136
  29. package/assets/claude-code/commands/agents/aristotle-analyst.md +157 -0
  30. package/assets/claude-code/commands/agents/aristotle-explorer.md +157 -0
  31. package/assets/claude-code/commands/agents/aristotle-forecaster.md +157 -0
  32. package/assets/claude-code/commands/agents/aristotle-validator.md +157 -0
  33. package/assets/{commands → claude-code/commands}/agents/assumption-excavator.md +49 -7
  34. package/assets/{commands → claude-code/commands}/agents/audit.md +156 -137
  35. package/assets/{commands → claude-code/commands}/agents/docs-validate.md +156 -134
  36. package/assets/{commands → claude-code/commands}/agents/frontend.md +156 -136
  37. package/assets/{commands → claude-code/commands}/agents/mcp-validate.md +156 -137
  38. package/assets/{commands → claude-code/commands}/agents/optimize.md +156 -134
  39. package/assets/{commands → claude-code/commands}/agents/pattern-analyzer.md +150 -127
  40. package/assets/{commands → claude-code/commands}/agents/prompt-quality.md +155 -135
  41. package/assets/claude-code/commands/agents/prompt-validate.md +155 -0
  42. package/assets/{commands → claude-code/commands}/agents/public-interface.md +156 -135
  43. package/assets/{commands → claude-code/commands}/agents/release.md +156 -136
  44. package/assets/{commands → claude-code/commands}/agents/security.md +156 -138
  45. package/assets/{commands → claude-code/commands}/agents/test-review.md +156 -137
  46. package/assets/{commands → claude-code/commands}/agents/type-safety.md +156 -136
  47. package/assets/{commands/agents/code-validate.md → claude-code/commands/agents/validate.md} +156 -135
  48. package/assets/claude-code/commands/agents/workflow-synthesis.md +157 -0
  49. package/assets/{commands → claude-code/commands}/pipelines/aristotle.md +8 -8
  50. package/assets/{commands → claude-code/commands}/pipelines/ship.md +8 -8
  51. package/assets/claude-code/commands/workflows/post-implementation.md +60 -0
  52. package/assets/claude-code/commands/workflows/pre-implementation.md +46 -0
  53. package/assets/{commands → claude-code/commands}/workflows/prompt-audit.md +2 -2
  54. package/assets/codex/agents/anxiety-reader-agent.toml +462 -0
  55. package/assets/codex/agents/api-contract-validator-agent.toml +738 -0
  56. package/assets/codex/agents/aristotle-analyst-agent.toml +750 -0
  57. package/assets/codex/agents/aristotle-explorer-agent.toml +155 -0
  58. package/assets/codex/agents/aristotle-forecaster-agent.toml +449 -0
  59. package/assets/codex/agents/aristotle-validator-agent.toml +424 -0
  60. package/assets/codex/agents/assumption-excavator-agent.toml +1126 -0
  61. package/assets/codex/agents/code-auditor-agent.toml +815 -0
  62. package/assets/codex/agents/code-optimizer-agent.toml +652 -0
  63. package/assets/codex/agents/code-validator-agent.toml +573 -0
  64. package/assets/codex/agents/docs-validator-agent.toml +468 -0
  65. package/assets/codex/agents/frontend-validator-agent.toml +598 -0
  66. package/assets/codex/agents/mcp-validator-agent.toml +580 -0
  67. package/assets/codex/agents/pre-implementation-architect-agent.toml +817 -0
  68. package/assets/codex/agents/prompt-engineer-agent.toml +922 -0
  69. package/assets/codex/agents/prompt-pattern-analyzer-agent.toml +689 -0
  70. package/assets/codex/agents/prompt-quality-validator-agent.toml +777 -0
  71. package/assets/codex/agents/public-interface-validator-agent.toml +695 -0
  72. package/assets/codex/agents/release-readiness-agent.toml +491 -0
  73. package/assets/codex/agents/security-analyst-agent.toml +847 -0
  74. package/assets/codex/agents/test-architect-agent.toml +615 -0
  75. package/assets/codex/agents/type-safety-validator-agent.toml +686 -0
  76. package/assets/codex/agents/workflow-synthesis-agent.toml +631 -0
  77. package/assets/gemini-cli/agents/anxiety-reader-agent.md +470 -0
  78. package/assets/gemini-cli/agents/api-contract-validator-agent.md +747 -0
  79. package/assets/gemini-cli/agents/aristotle-analyst-agent.md +758 -0
  80. package/assets/gemini-cli/agents/aristotle-explorer-agent.md +163 -0
  81. package/assets/gemini-cli/agents/aristotle-forecaster-agent.md +457 -0
  82. package/assets/gemini-cli/agents/aristotle-validator-agent.md +432 -0
  83. package/assets/gemini-cli/agents/assumption-excavator-agent.md +1134 -0
  84. package/assets/gemini-cli/agents/code-auditor-agent.md +827 -0
  85. package/assets/gemini-cli/agents/code-optimizer-agent.md +661 -0
  86. package/assets/gemini-cli/agents/code-validator-agent.md +582 -0
  87. package/assets/gemini-cli/agents/docs-validator-agent.md +477 -0
  88. package/assets/gemini-cli/agents/frontend-validator-agent.md +610 -0
  89. package/assets/gemini-cli/agents/mcp-validator-agent.md +589 -0
  90. package/assets/gemini-cli/agents/pre-implementation-architect-agent.md +826 -0
  91. package/assets/gemini-cli/agents/prompt-engineer-agent.md +931 -0
  92. package/assets/gemini-cli/agents/prompt-pattern-analyzer-agent.md +698 -0
  93. package/assets/gemini-cli/agents/prompt-quality-validator-agent.md +786 -0
  94. package/assets/gemini-cli/agents/public-interface-validator-agent.md +707 -0
  95. package/assets/gemini-cli/agents/release-readiness-agent.md +500 -0
  96. package/assets/gemini-cli/agents/security-analyst-agent.md +859 -0
  97. package/assets/gemini-cli/agents/test-architect-agent.md +624 -0
  98. package/assets/gemini-cli/agents/type-safety-validator-agent.md +695 -0
  99. package/assets/gemini-cli/agents/workflow-synthesis-agent.md +639 -0
  100. package/assets/gemini-cli/commands/agents/anxiety-reader.toml +155 -0
  101. package/assets/gemini-cli/commands/agents/api-contract.toml +154 -0
  102. package/assets/gemini-cli/commands/agents/architect.toml +154 -0
  103. package/assets/gemini-cli/commands/agents/aristotle-analyst.toml +155 -0
  104. package/assets/gemini-cli/commands/agents/aristotle-explorer.toml +155 -0
  105. package/assets/gemini-cli/commands/agents/aristotle-forecaster.toml +155 -0
  106. package/assets/gemini-cli/commands/agents/aristotle-validator.toml +155 -0
  107. package/assets/gemini-cli/commands/agents/assumption-excavator.toml +155 -0
  108. package/assets/gemini-cli/commands/agents/audit.toml +154 -0
  109. package/assets/gemini-cli/commands/agents/docs-validate.toml +154 -0
  110. package/assets/gemini-cli/commands/agents/frontend.toml +154 -0
  111. package/assets/gemini-cli/commands/agents/mcp-validate.toml +154 -0
  112. package/assets/gemini-cli/commands/agents/optimize.toml +154 -0
  113. package/assets/gemini-cli/commands/agents/pattern-analyzer.toml +148 -0
  114. package/assets/gemini-cli/commands/agents/prompt-quality.toml +153 -0
  115. package/assets/gemini-cli/commands/agents/prompt-validate.toml +153 -0
  116. package/assets/gemini-cli/commands/agents/public-interface.toml +154 -0
  117. package/assets/gemini-cli/commands/agents/release.toml +154 -0
  118. package/assets/gemini-cli/commands/agents/security.toml +154 -0
  119. package/assets/gemini-cli/commands/agents/test-review.toml +154 -0
  120. package/assets/gemini-cli/commands/agents/type-safety.toml +154 -0
  121. package/assets/gemini-cli/commands/agents/validate.toml +154 -0
  122. package/assets/gemini-cli/commands/agents/workflow-synthesis.toml +155 -0
  123. package/assets/gemini-cli/commands/pipelines/aristotle.toml +139 -0
  124. package/assets/gemini-cli/commands/pipelines/ship.toml +184 -0
  125. package/assets/gemini-cli/commands/workflows/post-implementation.toml +56 -0
  126. package/assets/gemini-cli/commands/workflows/pre-implementation.toml +42 -0
  127. package/assets/gemini-cli/commands/workflows/prompt-audit.toml +40 -0
  128. package/assets/opencode/agents/anxiety-reader-agent.md +472 -0
  129. package/assets/opencode/agents/api-contract-validator-agent.md +749 -0
  130. package/assets/opencode/agents/aristotle-analyst-agent.md +760 -0
  131. package/assets/opencode/agents/aristotle-explorer-agent.md +164 -0
  132. package/assets/opencode/agents/aristotle-forecaster-agent.md +459 -0
  133. package/assets/opencode/agents/aristotle-validator-agent.md +434 -0
  134. package/assets/opencode/agents/assumption-excavator-agent.md +1136 -0
  135. package/assets/opencode/agents/code-auditor-agent.md +826 -0
  136. package/assets/opencode/agents/code-optimizer-agent.md +663 -0
  137. package/assets/opencode/agents/code-validator-agent.md +584 -0
  138. package/assets/opencode/agents/docs-validator-agent.md +479 -0
  139. package/assets/opencode/agents/frontend-validator-agent.md +609 -0
  140. package/assets/opencode/agents/mcp-validator-agent.md +591 -0
  141. package/assets/opencode/agents/pre-implementation-architect-agent.md +828 -0
  142. package/assets/opencode/agents/prompt-engineer-agent.md +933 -0
  143. package/assets/opencode/agents/prompt-pattern-analyzer-agent.md +700 -0
  144. package/assets/opencode/agents/prompt-quality-validator-agent.md +788 -0
  145. package/assets/opencode/agents/public-interface-validator-agent.md +706 -0
  146. package/assets/opencode/agents/release-readiness-agent.md +502 -0
  147. package/assets/opencode/agents/security-analyst-agent.md +858 -0
  148. package/assets/opencode/agents/test-architect-agent.md +626 -0
  149. package/assets/opencode/agents/type-safety-validator-agent.md +697 -0
  150. package/assets/opencode/agents/workflow-synthesis-agent.md +641 -0
  151. package/dist/cli.js +49 -416
  152. package/dist/commands/helpers.d.ts +73 -0
  153. package/dist/commands/helpers.js +311 -0
  154. package/dist/commands/setup.d.ts +13 -0
  155. package/dist/commands/setup.js +93 -0
  156. package/dist/commands/uninstall.d.ts +3 -0
  157. package/dist/commands/uninstall.js +126 -0
  158. package/dist/commands/verify.d.ts +1 -0
  159. package/dist/commands/verify.js +28 -0
  160. package/dist/harnesses/claude-code.d.ts +1 -1
  161. package/dist/harnesses/claude-code.js +3 -1
  162. package/dist/harnesses/codex.js +6 -5
  163. package/dist/harnesses/gemini-cli.d.ts +4 -8
  164. package/dist/harnesses/gemini-cli.js +47 -21
  165. package/dist/harnesses/index.d.ts +10 -1
  166. package/dist/harnesses/index.js +11 -2
  167. package/dist/harnesses/opencode.d.ts +1 -1
  168. package/dist/harnesses/opencode.js +17 -8
  169. package/dist/harnesses/types.d.ts +19 -0
  170. package/dist/harnesses/types.js +2 -0
  171. package/dist/lib/asset-catalog.js +2 -2
  172. package/dist/lib/config-merger.d.ts +2 -1
  173. package/dist/lib/config-merger.js +15 -7
  174. package/dist/lib/file-ops.d.ts +5 -0
  175. package/dist/lib/file-ops.js +18 -3
  176. package/dist/lib/hash.d.ts +1 -1
  177. package/dist/lib/hash.js +2 -2
  178. package/dist/lib/manifest.d.ts +30 -1
  179. package/dist/lib/manifest.js +5 -7
  180. package/dist/lib/paths.d.ts +16 -1
  181. package/dist/lib/paths.js +31 -3
  182. package/dist/lib/settings-merger.d.ts +24 -9
  183. package/dist/lib/settings-merger.js +57 -22
  184. package/dist/lib/version.d.ts +2 -0
  185. package/dist/lib/version.js +10 -0
  186. package/dist/steps/agents.d.ts +1 -2
  187. package/dist/steps/agents.js +7 -18
  188. package/dist/steps/auth.d.ts +6 -0
  189. package/dist/steps/auth.js +19 -2
  190. package/dist/steps/cli.d.ts +53 -0
  191. package/dist/steps/cli.js +90 -0
  192. package/dist/steps/commands.d.ts +1 -1
  193. package/dist/steps/commands.js +20 -71
  194. package/dist/steps/detect.js +4 -0
  195. package/dist/steps/mcp.js +7 -15
  196. package/dist/steps/metrics.d.ts +12 -0
  197. package/dist/steps/metrics.js +52 -22
  198. package/dist/steps/shell.js +11 -1
  199. package/dist/steps/signup.d.ts +2 -2
  200. package/dist/steps/signup.js +9 -12
  201. package/dist/steps/verify.js +47 -8
  202. package/package.json +12 -11
  203. package/assets/agents/docs-validator-agent.md +0 -490
  204. package/assets/agents/release-readiness-agent.md +0 -482
  205. package/assets/commands/agents/aristotle-analyst.md +0 -116
  206. package/assets/commands/agents/aristotle-explorer.md +0 -93
  207. package/assets/commands/agents/aristotle-forecaster.md +0 -115
  208. package/assets/commands/agents/aristotle-validator.md +0 -115
  209. package/assets/commands/agents/prompt-validate.md +0 -136
  210. package/assets/commands/agents/workflow-synthesis.md +0 -102
  211. package/assets/commands/workflows/post-implementation.md +0 -577
  212. package/assets/commands/workflows/pre-implementation.md +0 -670
  213. /package/assets/{agents → claude-code/agents}/anxiety-reader-agent.md +0 -0
@@ -4,7 +4,7 @@
4
4
  * Installs agent-metrics tool files and configures post-agent hooks.
5
5
  * Only active for harnesses that support hooks (currently Claude Code only).
6
6
  */
7
- import { mkdir, readdir, copyFile, rm, access } from "node:fs/promises";
7
+ import { mkdir, readdir, copyFile, rm, access, readFile } from "node:fs/promises";
8
8
  import { join } from "node:path";
9
9
  /** Where agent-metrics dist files are installed (derived from profile) */
10
10
  function getMetricsToolDir(profile) {
@@ -18,30 +18,52 @@ export function getHookCommand(profile) {
18
18
  const toolDir = getMetricsToolDir(profile);
19
19
  if (!toolDir)
20
20
  throw new Error("No tool dir for this harness");
21
- return `"${process.execPath}" "${join(toolDir, "dist", "hook.js")}"`;
21
+ const nodePath = process.execPath;
22
+ const hookPath = join(toolDir, "dist", "hook.js");
23
+ if (nodePath.includes('"') || hookPath.includes('"')) {
24
+ throw new Error("Hook command paths must not contain double-quote characters");
25
+ }
26
+ return `"${nodePath}" "${hookPath}"`;
22
27
  }
23
28
  /**
24
29
  * Find the agent-metrics package source directory.
25
30
  * Looks for it as a sibling package in the monorepo or as an npm dependency.
31
+ *
32
+ * Also reads the source package.json's version so the manifest can record
33
+ * which agent-metrics version was actually copied — the shared version
34
+ * ledger across the setup↔agent-metrics seam.
26
35
  */
27
36
  async function findMetricsSource() {
28
- // Try to find the package via Node.js module resolution
29
37
  try {
30
38
  const resolved = import.meta.resolve("@uluops/agent-metrics");
31
39
  // resolved is like file:///path/to/dist/index.js — get the package root
32
40
  const distDir = new URL(".", resolved).pathname;
33
41
  const pkgRoot = join(distDir, "..");
34
- return pkgRoot;
42
+ const version = await readSourceVersion(pkgRoot);
43
+ return { pkgRoot, version };
44
+ }
45
+ catch {
46
+ return null;
47
+ }
48
+ }
49
+ async function readSourceVersion(pkgRoot) {
50
+ try {
51
+ const raw = await readFile(join(pkgRoot, "package.json"), "utf-8");
52
+ const parsed = JSON.parse(raw);
53
+ return typeof parsed.version === "string" ? parsed.version : null;
35
54
  }
36
55
  catch {
37
- // Not installed as dependency
56
+ return null;
38
57
  }
39
- return null;
40
58
  }
41
59
  /**
42
- * Copy agent-metrics dist files to the tool directory.
43
- * Copies all .js files needed for the hook and CLI.
60
+ * Read the installed agent-metrics version from the harness tree.
61
+ * Returns null when the file is missing or unparseable.
62
+ * Exported so verify.ts can detect drift between installed and source.
44
63
  */
64
+ export async function readInstalledMetricsVersion(toolDir) {
65
+ return readSourceVersion(toolDir);
66
+ }
45
67
  /** Copy .js files from a source dir to a dest dir, skipping test files. */
46
68
  async function copyJsDir(srcDir, destDir, dryRun) {
47
69
  let count = 0;
@@ -64,7 +86,13 @@ async function copyToolFiles(srcRoot, destRoot, dryRun) {
64
86
  const srcDist = join(srcRoot, "dist");
65
87
  const destDist = join(destRoot, "dist");
66
88
  const subDirs = ["commands", "display"];
89
+ // Replace, don't merge. The previous behavior copied new files over old
90
+ // ones without removing stale entries — if agent-metrics renames or
91
+ // removes a file in a future version, the stale file would persist on
92
+ // disk and shadow the new one. Wipe dist/ before repopulating so the
93
+ // installed tree matches the source tree exactly.
67
94
  if (!dryRun) {
95
+ await rm(destDist, { recursive: true, force: true });
68
96
  await mkdir(destDist, { recursive: true });
69
97
  for (const sub of subDirs) {
70
98
  await mkdir(join(destDist, sub), { recursive: true });
@@ -74,7 +102,7 @@ async function copyToolFiles(srcRoot, destRoot, dryRun) {
74
102
  for (const sub of subDirs) {
75
103
  filesCopied += await copyJsDir(join(srcDist, sub), join(destDist, sub), dryRun);
76
104
  }
77
- // Copy package.json (needed for CLI bin resolution)
105
+ // Copy package.json (needed for CLI bin resolution and version detection)
78
106
  try {
79
107
  if (!dryRun) {
80
108
  await copyFile(join(srcRoot, "package.json"), join(destRoot, "package.json"));
@@ -92,25 +120,22 @@ async function copyToolFiles(srcRoot, destRoot, dryRun) {
92
120
  */
93
121
  export async function installMetrics(profile, dryRun) {
94
122
  if (!profile.hooks || !profile.paths.toolsDir || !profile.paths.settingsPath) {
95
- return { toolFilesCopied: 0, hookConfigured: false, skippedReason: "no-hook-support" };
123
+ return {
124
+ toolFilesCopied: 0,
125
+ hookConfigured: false,
126
+ hooksInstalledVersion: null,
127
+ skippedReason: "no-hook-support",
128
+ };
96
129
  }
97
130
  const toolDir = profile.paths.toolsDir;
98
131
  const settingsPath = profile.paths.settingsPath;
99
- const srcRoot = await findMetricsSource();
132
+ const source = await findMetricsSource();
100
133
  let toolFilesCopied = 0;
101
- if (srcRoot) {
134
+ if (source) {
102
135
  if (!dryRun) {
103
136
  await mkdir(toolDir, { recursive: true });
104
137
  }
105
- toolFilesCopied = await copyToolFiles(srcRoot, toolDir, dryRun);
106
- }
107
- else {
108
- try {
109
- await access(join(toolDir, "dist", "hook.js"));
110
- }
111
- catch {
112
- // Not found anywhere
113
- }
138
+ toolFilesCopied = await copyToolFiles(source.pkgRoot, toolDir, dryRun);
114
139
  }
115
140
  let hookConfigured = false;
116
141
  const hookJsPath = join(toolDir, "dist", "hook.js");
@@ -123,7 +148,12 @@ export async function installMetrics(profile, dryRun) {
123
148
  else if (hookJsExists && dryRun) {
124
149
  hookConfigured = true;
125
150
  }
126
- return { toolFilesCopied, hookConfigured };
151
+ // Prefer the source version (from import.meta.resolve); fall back to reading
152
+ // the just-copied package.json from the harness tree so the manifest still
153
+ // records something useful when the source resolution returned null but a
154
+ // prior install left files behind.
155
+ const hooksInstalledVersion = source?.version ?? (hookJsExists ? await readInstalledMetricsVersion(toolDir) : null);
156
+ return { toolFilesCopied, hookConfigured, hooksInstalledVersion };
127
157
  }
128
158
  /**
129
159
  * Uninstall agent-metrics: remove hook and tool files.
@@ -1,5 +1,8 @@
1
1
  import { readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
2
3
  import { atomicWrite } from "../lib/atomic-write.js";
4
+ import { backupFile } from "../lib/file-ops.js";
5
+ import { getUluopsDir } from "../lib/paths.js";
3
6
  const FENCE_START = "# --- UluOps (managed by @uluops/setup) ---";
4
7
  const FENCE_END = "# --- /UluOps ---";
5
8
  /** Characters safe for shell variable values (no metacharacters). */
@@ -16,12 +19,15 @@ export async function writeShellExport(profilePath, apiKey, dryRun) {
16
19
  }
17
20
  catch {
18
21
  if (!dryRun) {
19
- await atomicWrite(profilePath, block + "\n");
22
+ await atomicWrite(profilePath, block + "\n", { mode: 0o600 });
20
23
  }
21
24
  return;
22
25
  }
23
26
  const startIdx = content.indexOf(FENCE_START);
24
27
  const endIdx = content.indexOf(FENCE_END);
28
+ if (!dryRun) {
29
+ await backupProfile(profilePath);
30
+ }
25
31
  if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
26
32
  // Replace existing fenced block (use last FENCE_END after FENCE_START to handle duplicates)
27
33
  const before = content.slice(0, startIdx);
@@ -48,8 +54,12 @@ export async function removeShellExport(profilePath) {
48
54
  const startIdx = content.indexOf(FENCE_START);
49
55
  const endIdx = content.indexOf(FENCE_END);
50
56
  if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
57
+ await backupProfile(profilePath);
51
58
  const before = content.slice(0, startIdx);
52
59
  const after = content.slice(endIdx + FENCE_END.length);
53
60
  await atomicWrite(profilePath, (before + after).replace(/\n{3,}/g, "\n\n"));
54
61
  }
55
62
  }
63
+ async function backupProfile(profilePath) {
64
+ await backupFile(profilePath, join(getUluopsDir(), "backups", "shell"));
65
+ }
@@ -4,7 +4,7 @@ import type { AuthResult } from "./auth.js";
4
4
  * but all are non-blocking — server validation is the authority.
5
5
  * @internal Exported for testing only — not part of the public API.
6
6
  */
7
- declare function validatePassword(password: string): true;
7
+ declare function hintPassword(password: string): true;
8
8
  /** @internal Exported for testing only — not part of the public API. */
9
9
  declare function validateEmail(email: string): string | true;
10
10
  /**
@@ -13,4 +13,4 @@ declare function validateEmail(email: string): string | true;
13
13
  */
14
14
  export declare function signup(): Promise<AuthResult>;
15
15
  /** @internal Exported for testing only — not part of the public API. */
16
- export { validatePassword, validateEmail };
16
+ export { hintPassword, validateEmail };
@@ -4,7 +4,7 @@ const API_BASE = "https://api.uluops.ai/api/v1";
4
4
  * but all are non-blocking — server validation is the authority.
5
5
  * @internal Exported for testing only — not part of the public API.
6
6
  */
7
- function validatePassword(password) {
7
+ function hintPassword(password) {
8
8
  if (password.length < 8)
9
9
  console.warn(" ⚠ Hint: server may require at least 8 characters");
10
10
  else if (password.length > 128)
@@ -38,25 +38,19 @@ export async function signup() {
38
38
  const pwd = await password({
39
39
  message: "Password",
40
40
  mask: "*",
41
- validate: validatePassword,
41
+ validate: hintPassword,
42
42
  });
43
43
  // Register
44
- const registerRes = await callApi(`${API_BASE}/auth/register`, "POST", { email, password: pwd });
45
- if (!registerRes.data?.sessionToken) {
46
- throw new Error("Registration succeeded but response missing session token");
47
- }
44
+ const registerRes = await callApi(`${API_BASE}/auth/register`, "POST", { email, password: pwd }, undefined, (res) => !!res.data?.sessionToken && typeof res.data.user?.email === "string");
48
45
  const sessionToken = registerRes.data.sessionToken;
49
46
  // Create API key using the session
50
- const keyRes = await callApi(`${API_BASE}/auth/keys`, "POST", { name: "Setup CLI" }, sessionToken);
51
- if (!keyRes.data?.key) {
52
- throw new Error("API key creation succeeded but response missing key");
53
- }
47
+ const keyRes = await callApi(`${API_BASE}/auth/keys`, "POST", { name: "Setup CLI" }, sessionToken, (res) => !!res.data?.key);
54
48
  return {
55
49
  apiKey: keyRes.data.key,
56
50
  email: registerRes.data.user?.email ?? email,
57
51
  };
58
52
  }
59
- async function callApi(url, method, body, bearerToken) {
53
+ async function callApi(url, method, body, bearerToken, validate) {
60
54
  const headers = {
61
55
  "Content-Type": "application/json",
62
56
  };
@@ -83,6 +77,9 @@ async function callApi(url, method, body, bearerToken) {
83
77
  if (typeof body !== "object" || body === null) {
84
78
  throw new Error("Unexpected API response shape");
85
79
  }
80
+ if (validate && !validate(body)) {
81
+ throw new Error("API response failed structural validation");
82
+ }
86
83
  return body;
87
84
  }
88
85
  // Handle known error codes
@@ -101,4 +98,4 @@ async function callApi(url, method, body, bearerToken) {
101
98
  throw new Error(`Signup failed (${res.status}): ${message}`);
102
99
  }
103
100
  /** @internal Exported for testing only — not part of the public API. */
104
- export { validatePassword, validateEmail };
101
+ export { hintPassword, validateEmail };
@@ -1,8 +1,9 @@
1
- import { readdir, access } from "node:fs/promises";
1
+ import { readdir, access, stat } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { loadManifest } from "../lib/manifest.js";
4
4
  import { getHealthTimeout } from "../lib/health.js";
5
5
  import { getProfile } from "../harnesses/index.js";
6
+ import { readInstalledMetricsVersion } from "./metrics.js";
6
7
  /** Verify a single harness entry, appending results to checks. Returns false if any check fails. */
7
8
  async function verifyHarness(harnessName, hm, checks) {
8
9
  let allOk = true;
@@ -14,7 +15,24 @@ async function verifyHarness(harnessName, hm, checks) {
14
15
  checks.push({ label: `Harness: ${harnessName}`, passed: false, detail: "Unknown harness in manifest" });
15
16
  return false;
16
17
  }
17
- // MCP config
18
+ // 1. Readiness Check (Harness Restart)
19
+ try {
20
+ const configStat = await stat(hm.mcpConfigPath);
21
+ const installedTime = new Date(hm.installedAt).getTime();
22
+ const configTime = configStat.mtimeMs;
23
+ // If config was modified AFTER install, it's a proxy for the harness having read it.
24
+ // We add a 100ms buffer to handle near-simultaneous writes.
25
+ const isReady = configTime > (installedTime + 100);
26
+ checks.push({
27
+ label: `[${profile.displayName}] Readiness`,
28
+ passed: true,
29
+ detail: isReady ? "Active (loaded)" : "Waiting for restart",
30
+ });
31
+ }
32
+ catch {
33
+ // If we can't stat it, we'll catch the error in the MCP config check below.
34
+ }
35
+ // 2. MCP config
18
36
  try {
19
37
  const config = await profile.mcpConfig.read(hm.mcpConfigPath);
20
38
  if (profile.mcpConfig.check(config)) {
@@ -49,14 +67,16 @@ async function verifyHarness(harnessName, hm, checks) {
49
67
  // Command files
50
68
  if (hm.commands.length > 0) {
51
69
  const commandsDir = join(hm.defsPath, "commands");
52
- let found = 0;
53
- for (const cmd of hm.commands) {
70
+ const cmdResults = await Promise.all(hm.commands.map(async (cmd) => {
54
71
  try {
55
72
  await access(join(commandsDir, cmd));
56
- found++;
73
+ return true;
57
74
  }
58
- catch { /* Missing */ }
59
- }
75
+ catch {
76
+ return false;
77
+ }
78
+ }));
79
+ const found = cmdResults.filter(Boolean).length;
60
80
  checks.push({
61
81
  label: `[${profile.displayName}] ${found}/${hm.commands.length} commands`,
62
82
  passed: found === hm.commands.length,
@@ -77,7 +97,26 @@ async function verifyHarness(harnessName, hm, checks) {
77
97
  catch { /* Missing */ }
78
98
  }
79
99
  if (hookPresent && hookFilePresent) {
80
- checks.push({ label: `[${profile.displayName}] Agent metrics hook configured`, passed: true });
100
+ // Existence is necessary but not sufficient check version drift against the
101
+ // currently-resolvable agent-metrics. Without this, verify cannot detect that
102
+ // a fresh setup release is shipping while the installed hook is stale.
103
+ const installedVersion = profile.paths.toolsDir
104
+ ? await readInstalledMetricsVersion(profile.paths.toolsDir)
105
+ : null;
106
+ const recordedVersion = hm.hooksInstalledVersion ?? null;
107
+ const detail = installedVersion
108
+ ? `v${installedVersion}${recordedVersion && recordedVersion !== installedVersion ? ` (manifest records v${recordedVersion} — out of sync)` : ""}`
109
+ : "version unknown";
110
+ checks.push({
111
+ label: `[${profile.displayName}] Agent metrics hook configured`,
112
+ passed: true,
113
+ detail,
114
+ });
115
+ if (recordedVersion && installedVersion && recordedVersion !== installedVersion) {
116
+ // Drift between manifest record and on-disk hook copy — not a hard failure
117
+ // because verify is read-only, but surface it so the user re-runs setup.
118
+ allOk = false;
119
+ }
81
120
  }
82
121
  else {
83
122
  const missing = [
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@uluops/setup",
3
- "version": "0.4.0",
3
+ "version": "0.6.3",
4
4
  "description": "Zero-friction installer for UluOps agentic harnesses",
5
- "license": "SEE LICENSE IN LICENSE",
5
+ "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/Uluops/-uluops-setup.git"
@@ -32,6 +32,7 @@
32
32
  "dist/lib/**",
33
33
  "dist/steps/**",
34
34
  "dist/harnesses/**",
35
+ "dist/commands/**",
35
36
  "assets"
36
37
  ],
37
38
  "engines": {
@@ -46,16 +47,16 @@
46
47
  "prepublishOnly": "npm run build"
47
48
  },
48
49
  "dependencies": {
49
- "@inquirer/prompts": "^7.0.0",
50
- "@uluops/agent-metrics": "^0.2.0",
51
- "chalk": "^5.0.0",
52
- "commander": "^12.0.0",
53
- "jsonc-parser": "^3.3.1"
50
+ "@inquirer/prompts": "7.10.1",
51
+ "@uluops/agent-metrics": "0.4.0",
52
+ "chalk": "5.6.2",
53
+ "commander": "12.1.0",
54
+ "jsonc-parser": "3.3.1"
54
55
  },
55
56
  "devDependencies": {
56
- "@types/node": "^22.0.0",
57
- "tsx": "^4.0.0",
58
- "typescript": "^5.7.0",
59
- "vitest": "^3.0.0"
57
+ "@types/node": "22.19.15",
58
+ "tsx": "4.21.0",
59
+ "typescript": "5.9.3",
60
+ "vitest": "3.2.4"
60
61
  }
61
62
  }