archal 0.9.11 → 0.9.12

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/dist/index.cjs CHANGED
@@ -86957,11 +86957,18 @@ function checksumDir(dir) {
86957
86957
  for (const entry of entries) {
86958
86958
  if (entry.name === MANIFEST_FILE2) continue;
86959
86959
  const p = (0, import_node_path51.join)(dir, entry.name);
86960
+ const nameBytes = Buffer.from(entry.name, "utf8");
86960
86961
  if (entry.isDirectory()) {
86961
- hash2.update(`${entry.name}:dir:${checksumDir(p)}`);
86962
+ const sub = checksumDir(p) ?? "";
86963
+ const subBytes = Buffer.from(sub, "utf8");
86964
+ hash2.update(`d:${nameBytes.length}:${sub ? subBytes.length : 0}:`);
86965
+ hash2.update(nameBytes);
86966
+ hash2.update(subBytes);
86962
86967
  } else {
86963
- hash2.update(`${entry.name}:`);
86964
- hash2.update((0, import_node_fs51.readFileSync)(p));
86968
+ const content = (0, import_node_fs51.readFileSync)(p);
86969
+ hash2.update(`f:${nameBytes.length}:${content.length}:`);
86970
+ hash2.update(nameBytes);
86971
+ hash2.update(content);
86965
86972
  }
86966
86973
  }
86967
86974
  return hash2.digest("hex");
@@ -86992,6 +86999,16 @@ function parseTargetFlag(value) {
86992
86999
  }
86993
87000
 
86994
87001
  // src/skills/installer.ts
87002
+ var ARCHAL_SKILL_PREFIX = "archal-";
87003
+ var SAFE_SKILL_NAME_RE = /^archal-[a-z0-9][a-z0-9-]*$/;
87004
+ function isSafeSkillName(destName) {
87005
+ return SAFE_SKILL_NAME_RE.test(destName);
87006
+ }
87007
+ function isValidManifestEntry(value) {
87008
+ if (!value || typeof value !== "object") return false;
87009
+ const entry = value;
87010
+ return typeof entry["checksum"] === "string" && typeof entry["version"] === "string";
87011
+ }
86995
87012
  var RESET8 = "\x1B[0m";
86996
87013
  var BOLD7 = "\x1B[1m";
86997
87014
  var DIM8 = "\x1B[2m";
@@ -87008,11 +87025,12 @@ async function runInstaller({
87008
87025
  }) {
87009
87026
  const log3 = silent ? () => {
87010
87027
  } : (msg) => process.stdout.write(msg);
87028
+ const isNonInteractive = nonInteractive || !process.stdin.isTTY;
87011
87029
  const skillDirs = (0, import_node_fs52.readdirSync)(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
87012
87030
  const selected = await resolveTargets({
87013
87031
  cwd,
87014
87032
  requested: requestedTargets ?? null,
87015
- nonInteractive,
87033
+ nonInteractive: isNonInteractive,
87016
87034
  log: log3,
87017
87035
  version: version3
87018
87036
  });
@@ -87022,7 +87040,9 @@ async function runInstaller({
87022
87040
  return { installed: 0, targets: [], skipped: [], version: version3 };
87023
87041
  }
87024
87042
  let installed = 0;
87043
+ let removed = 0;
87025
87044
  const skipped = [];
87045
+ const sourceDestNames = new Set(skillDirs.map((s) => `${ARCHAL_SKILL_PREFIX}${s}`));
87026
87046
  for (const target of selected) {
87027
87047
  const platformDir = (0, import_node_path52.join)(cwd, target.skillsPath);
87028
87048
  (0, import_node_fs52.mkdirSync)(platformDir, { recursive: true });
@@ -87034,35 +87054,44 @@ async function runInstaller({
87034
87054
  };
87035
87055
  for (const skill of skillDirs) {
87036
87056
  const src = (0, import_node_path52.join)(skillsDir, skill);
87037
- const destName = `archal-${skill}`;
87057
+ const destName = `${ARCHAL_SKILL_PREFIX}${skill}`;
87038
87058
  const dest = (0, import_node_path52.join)(platformDir, destName);
87039
87059
  const sourceChecksum = checksumDir(src);
87040
87060
  const existingChecksum = checksumDir(dest);
87041
87061
  const manifestEntry = manifest?.skills?.[destName];
87042
- const userEdited = existingChecksum && manifestEntry?.checksum && existingChecksum !== manifestEntry.checksum && existingChecksum !== sourceChecksum;
87062
+ let userEdited = false;
87063
+ let migrationCase = false;
87064
+ if (existingChecksum) {
87065
+ if (manifestEntry?.checksum) {
87066
+ userEdited = existingChecksum !== manifestEntry.checksum && existingChecksum !== sourceChecksum;
87067
+ } else if (existingChecksum !== sourceChecksum) {
87068
+ userEdited = true;
87069
+ migrationCase = true;
87070
+ }
87071
+ }
87043
87072
  if (userEdited && !yes) {
87044
- if (nonInteractive) {
87045
- skipped.push(`${target.id}/${destName} (user-edited, --yes required)`);
87046
- nextManifest.skills[destName] = {
87047
- version: manifestEntry?.version ?? "unknown",
87048
- checksum: existingChecksum,
87049
- userEdited: true
87050
- };
87073
+ const message = migrationCase ? `${target.label}: ${destName} exists from an earlier install and may contain local edits. Overwrite with archal@${version3}?` : `${target.label}: ${destName} has local edits. Overwrite?`;
87074
+ const skipSuffix = migrationCase ? "legacy install, --yes required" : "user-edited, --yes required";
87075
+ const keptSuffix = migrationCase ? "kept legacy copy" : "kept user edits";
87076
+ const preservedEntry = {
87077
+ version: manifestEntry?.version ?? (migrationCase ? "legacy" : "unknown"),
87078
+ checksum: manifestEntry?.checksum ?? sourceChecksum ?? "",
87079
+ userEdited: true
87080
+ };
87081
+ if (isNonInteractive) {
87082
+ skipped.push(`${target.id}/${destName} (${skipSuffix})`);
87083
+ nextManifest.skills[destName] = preservedEntry;
87051
87084
  continue;
87052
87085
  }
87053
87086
  const { overwrite } = await (0, import_prompts.default)({
87054
87087
  type: "confirm",
87055
87088
  name: "overwrite",
87056
- message: `${target.label}: ${destName} has local edits. Overwrite?`,
87057
- initial: false
87089
+ message,
87090
+ initial: migrationCase ? true : false
87058
87091
  });
87059
87092
  if (!overwrite) {
87060
- skipped.push(`${target.id}/${destName} (kept user edits)`);
87061
- nextManifest.skills[destName] = {
87062
- version: manifestEntry?.version ?? "unknown",
87063
- checksum: existingChecksum,
87064
- userEdited: true
87065
- };
87093
+ skipped.push(`${target.id}/${destName} (${keptSuffix})`);
87094
+ nextManifest.skills[destName] = preservedEntry;
87066
87095
  continue;
87067
87096
  }
87068
87097
  }
@@ -87076,6 +87105,49 @@ async function runInstaller({
87076
87105
  };
87077
87106
  installed++;
87078
87107
  }
87108
+ if (manifest?.skills) {
87109
+ for (const [destName, rawEntry] of Object.entries(manifest.skills)) {
87110
+ if (!isSafeSkillName(destName)) {
87111
+ continue;
87112
+ }
87113
+ if (!isValidManifestEntry(rawEntry)) {
87114
+ continue;
87115
+ }
87116
+ const entry = rawEntry;
87117
+ if (sourceDestNames.has(destName)) continue;
87118
+ const dest = (0, import_node_path52.join)(platformDir, destName);
87119
+ if (!(0, import_node_fs52.existsSync)(dest)) continue;
87120
+ const existingChecksum = checksumDir(dest);
87121
+ const userEdited = existingChecksum !== entry.checksum;
87122
+ if (userEdited && !yes) {
87123
+ const preservedEntry = {
87124
+ version: entry.version,
87125
+ checksum: entry.checksum,
87126
+ userEdited: true
87127
+ };
87128
+ if (isNonInteractive) {
87129
+ skipped.push(
87130
+ `${target.id}/${destName} (removed upstream, edits detected \u2014 --yes required)`
87131
+ );
87132
+ nextManifest.skills[destName] = preservedEntry;
87133
+ continue;
87134
+ }
87135
+ const { remove } = await (0, import_prompts.default)({
87136
+ type: "confirm",
87137
+ name: "remove",
87138
+ message: `${target.label}: ${destName} was removed upstream but has local edits. Delete it?`,
87139
+ initial: false
87140
+ });
87141
+ if (!remove) {
87142
+ skipped.push(`${target.id}/${destName} (kept local copy)`);
87143
+ nextManifest.skills[destName] = preservedEntry;
87144
+ continue;
87145
+ }
87146
+ }
87147
+ (0, import_node_fs52.rmSync)(dest, { recursive: true, force: true });
87148
+ removed++;
87149
+ }
87150
+ }
87079
87151
  writeManifest(platformDir, nextManifest);
87080
87152
  }
87081
87153
  const targetLabels = selected.map((t) => t.label).join(", ");
@@ -87084,6 +87156,10 @@ ${GREEN5}${BOLD7}Archal skills installed${RESET8} ${DIM8}(v${version3})${RESET8}
87084
87156
  `);
87085
87157
  log3(`${DIM8}${installed} skill(s) \u2192 ${targetLabels}${RESET8}
87086
87158
  `);
87159
+ if (removed > 0) {
87160
+ log3(`${DIM8}${removed} skill(s) removed (no longer shipped)${RESET8}
87161
+ `);
87162
+ }
87087
87163
  if (skipped.length) {
87088
87164
  log3(`${YELLOW4}Skipped:${RESET8}
87089
87165
  `);
@@ -87143,7 +87219,7 @@ ${BOLD7}Install Archal skills${RESET8} ${DIM8}(v${version3})${RESET8}
87143
87219
  onCancel: () => {
87144
87220
  log3(`${YELLOW4}Cancelled.${RESET8}
87145
87221
  `);
87146
- process.exit(0);
87222
+ process.exit(130);
87147
87223
  }
87148
87224
  }
87149
87225
  );
@@ -87155,11 +87231,27 @@ ${BOLD7}Install Archal skills${RESET8} ${DIM8}(v${version3})${RESET8}
87155
87231
  init_version();
87156
87232
  init_errors6();
87157
87233
  function detectPackageManager2(cwd) {
87234
+ const fromPackageManager = readPackageManagerField(cwd);
87235
+ if (fromPackageManager) return fromPackageManager;
87158
87236
  if ((0, import_node_fs53.existsSync)((0, import_node_path53.join)(cwd, "pnpm-lock.yaml"))) return "pnpm";
87159
- if ((0, import_node_fs53.existsSync)((0, import_node_path53.join)(cwd, "bun.lockb"))) return "bun";
87237
+ if ((0, import_node_fs53.existsSync)((0, import_node_path53.join)(cwd, "bun.lockb")) || (0, import_node_fs53.existsSync)((0, import_node_path53.join)(cwd, "bun.lock"))) return "bun";
87160
87238
  if ((0, import_node_fs53.existsSync)((0, import_node_path53.join)(cwd, "yarn.lock"))) return "yarn";
87161
87239
  return "npm";
87162
87240
  }
87241
+ function readPackageManagerField(cwd) {
87242
+ try {
87243
+ const pkg = JSON.parse((0, import_node_fs53.readFileSync)((0, import_node_path53.join)(cwd, "package.json"), "utf8"));
87244
+ const raw = typeof pkg.packageManager === "string" ? pkg.packageManager : "";
87245
+ const name = raw.split("@")[0];
87246
+ if (name === "pnpm" || name === "yarn" || name === "bun" || name === "npm") {
87247
+ return name;
87248
+ }
87249
+ return null;
87250
+ } catch {
87251
+ return null;
87252
+ }
87253
+ }
87254
+ var SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
87163
87255
  function hasPackageJson(cwd) {
87164
87256
  return (0, import_node_fs53.existsSync)((0, import_node_path53.join)(cwd, "package.json"));
87165
87257
  }
@@ -87171,26 +87263,61 @@ function alreadyHasArchalDep(cwd) {
87171
87263
  return false;
87172
87264
  }
87173
87265
  }
87266
+ function looksLikeSkillsDir(candidate) {
87267
+ if (!(0, import_node_fs53.existsSync)(candidate)) return false;
87268
+ let entries;
87269
+ try {
87270
+ entries = (0, import_node_fs53.readdirSync)(candidate);
87271
+ } catch {
87272
+ return false;
87273
+ }
87274
+ for (const name of entries) {
87275
+ const child = (0, import_node_path53.join)(candidate, name);
87276
+ try {
87277
+ if (!(0, import_node_fs53.statSync)(child).isDirectory()) continue;
87278
+ if ((0, import_node_fs53.existsSync)((0, import_node_path53.join)(child, "SKILL.md"))) return true;
87279
+ } catch {
87280
+ }
87281
+ }
87282
+ return false;
87283
+ }
87284
+ function findRepoRoot(start) {
87285
+ let dir = start;
87286
+ for (let i = 0; i < 10; i++) {
87287
+ if ((0, import_node_fs53.existsSync)((0, import_node_path53.join)(dir, "pnpm-workspace.yaml"))) return dir;
87288
+ const parent = (0, import_node_path53.dirname)(dir);
87289
+ if (parent === dir) return null;
87290
+ dir = parent;
87291
+ }
87292
+ return null;
87293
+ }
87174
87294
  function resolveSkillsDir() {
87175
87295
  const here = typeof __dirname === "string" ? __dirname : process.cwd();
87296
+ const repoRoot = findRepoRoot(here) ?? findRepoRoot(process.cwd());
87176
87297
  const candidates = [
87177
87298
  (0, import_node_path53.resolve)(here, "..", "skills"),
87178
- (0, import_node_path53.resolve)(here, "..", "..", "..", "packages", "archal-skills", "skills"),
87179
- (0, import_node_path53.resolve)(process.cwd(), "packages", "archal-skills", "skills")
87299
+ ...repoRoot ? [(0, import_node_path53.resolve)(repoRoot, "packages", "archal", "skills")] : [],
87300
+ (0, import_node_path53.resolve)(process.cwd(), "packages", "archal", "skills"),
87301
+ (0, import_node_path53.resolve)(process.cwd(), "skills")
87180
87302
  ];
87181
87303
  for (const candidate of candidates) {
87182
- if ((0, import_node_fs53.existsSync)(candidate)) return candidate;
87304
+ if (looksLikeSkillsDir(candidate)) return candidate;
87183
87305
  }
87184
87306
  throw new CliRuntimeError(
87185
- "Could not locate the Archal skills directory. Reinstall archal, or run `npx @archal/skills` directly."
87307
+ "Could not locate the Archal skills directory. This looks like a broken archal install \u2014 reinstall with `npm install archal` (or your package manager equivalent) and retry."
87186
87308
  );
87187
87309
  }
87188
87310
  function runPmAdd(pm, cwd, spec) {
87189
87311
  const args = pm === "npm" ? ["install", "--save-dev", spec] : ["add", "-D", spec];
87190
87312
  const result = (0, import_node_child_process10.spawnSync)(pm, args, { cwd, stdio: "inherit" });
87313
+ if (result.error) {
87314
+ throw new CliRuntimeError(
87315
+ `Failed to launch ${pm}: ${result.error.message}. Install ${pm} (or install \`archal\` manually) and re-run \`archal init\`.`
87316
+ );
87317
+ }
87191
87318
  if (result.status !== 0) {
87192
87319
  throw new CliRuntimeError(
87193
- `Failed to install ${spec} with ${pm}. Install manually and re-run \`archal init\`.`
87320
+ `Failed to install ${spec} with ${pm} (exit ${result.status}). Install manually and re-run \`archal init\`.`
87194
87321
  );
87195
87322
  }
87196
87323
  }
@@ -87209,29 +87336,71 @@ function createInitCommand() {
87209
87336
  throw new CliRuntimeError(err instanceof Error ? err.message : String(err));
87210
87337
  }
87211
87338
  }
87212
- if (!opts.skillsOnly) {
87213
- if (!hasPackageJson(cwd)) {
87214
- process.stderr.write(
87215
- "No package.json in cwd \u2014 skipping devDependency install. Install archal globally (`npm i -g archal`) or initialize a package.json first.\n"
87216
- );
87217
- } else if (alreadyHasArchalDep(cwd)) {
87218
- process.stdout.write(`archal already a dependency \u2014 skipping install.
87339
+ let cliInstalled = true;
87340
+ const hadPackageJson = hasPackageJson(cwd);
87341
+ const startedAt = Date.now();
87342
+ try {
87343
+ if (!opts.skillsOnly) {
87344
+ if (!SEMVER_RE.test(CLI_VERSION)) {
87345
+ throw new CliRuntimeError(
87346
+ `Invalid CLI_VERSION "${CLI_VERSION}" \u2014 refusing to install. Reinstall archal and retry.`
87347
+ );
87348
+ }
87349
+ if (!hadPackageJson) {
87350
+ process.stderr.write(
87351
+ "No package.json in cwd \u2014 skipping devDependency install. Install archal globally (`npm i -g archal`) or initialize a package.json first.\n"
87352
+ );
87353
+ cliInstalled = false;
87354
+ } else if (alreadyHasArchalDep(cwd)) {
87355
+ process.stdout.write(`archal already a dependency \u2014 skipping install.
87219
87356
  `);
87220
- } else {
87221
- const pm = detectPackageManager2(cwd);
87222
- process.stdout.write(`Installing archal@${CLI_VERSION} with ${pm}\u2026
87357
+ } else {
87358
+ const pm = detectPackageManager2(cwd);
87359
+ process.stdout.write(`Installing archal@${CLI_VERSION} with ${pm}\u2026
87223
87360
  `);
87224
- runPmAdd(pm, cwd, `archal@${CLI_VERSION}`);
87361
+ runPmAdd(pm, cwd, `archal@${CLI_VERSION}`);
87362
+ }
87363
+ } else {
87364
+ cliInstalled = alreadyHasArchalDep(cwd);
87365
+ }
87366
+ const result = await runInstaller({
87367
+ cwd,
87368
+ targets: targets ?? void 0,
87369
+ nonInteractive: opts.nonInteractive === true,
87370
+ yes: opts.yes === true,
87371
+ skillsDir: resolveSkillsDir(),
87372
+ version: CLI_VERSION
87373
+ });
87374
+ if (opts.skillsOnly && !cliInstalled) {
87375
+ process.stdout.write(
87376
+ `Note: --skills-only left out the archal runner. Run \`npm install -D archal\` (or pnpm / yarn / bun equivalent) before \`archal run\`.
87377
+ `
87378
+ );
87225
87379
  }
87380
+ captureCliEvent("cli_init_completed", {
87381
+ skills_only: Boolean(opts.skillsOnly),
87382
+ non_interactive: opts.nonInteractive === true,
87383
+ yes: opts.yes === true,
87384
+ target_count: result.targets.length,
87385
+ targets: result.targets,
87386
+ installed: result.installed,
87387
+ skipped: result.skipped.length,
87388
+ had_package_json: hadPackageJson,
87389
+ cli_installed: cliInstalled,
87390
+ duration_ms: Date.now() - startedAt
87391
+ });
87392
+ } catch (err) {
87393
+ captureCliEvent("cli_init_failed", {
87394
+ skills_only: Boolean(opts.skillsOnly),
87395
+ non_interactive: opts.nonInteractive === true,
87396
+ yes: opts.yes === true,
87397
+ had_package_json: hadPackageJson,
87398
+ error_name: err instanceof Error ? err.constructor.name : "Unknown",
87399
+ error_message: err instanceof Error ? err.message : String(err),
87400
+ duration_ms: Date.now() - startedAt
87401
+ });
87402
+ throw err;
87226
87403
  }
87227
- await runInstaller({
87228
- cwd,
87229
- targets: targets ?? void 0,
87230
- nonInteractive: opts.nonInteractive === true,
87231
- yes: opts.yes === true,
87232
- skillsDir: resolveSkillsDir(),
87233
- version: CLI_VERSION
87234
- });
87235
87404
  }
87236
87405
  );
87237
87406
  }
@@ -87359,6 +87528,20 @@ function readManifestAt(path2) {
87359
87528
  return null;
87360
87529
  }
87361
87530
  }
87531
+ function compareSemver(a, b) {
87532
+ const parse3 = (v) => {
87533
+ const m = /^(\d+)\.(\d+)\.(\d+)(?:[-+][0-9A-Za-z.-]+)?$/.exec(v);
87534
+ if (!m) return null;
87535
+ return [Number(m[1]), Number(m[2]), Number(m[3])];
87536
+ };
87537
+ const pa = parse3(a);
87538
+ const pb = parse3(b);
87539
+ if (!pa || !pb) return null;
87540
+ if (pa[0] !== pb[0]) return pa[0] - pb[0];
87541
+ if (pa[1] !== pb[1]) return pa[1] - pb[1];
87542
+ if (pa[2] !== pb[2]) return pa[2] - pb[2];
87543
+ return 0;
87544
+ }
87362
87545
  function maybePrintSkillDriftBanner(cwd = process.cwd()) {
87363
87546
  try {
87364
87547
  const stale = [];
@@ -87367,7 +87550,8 @@ function maybePrintSkillDriftBanner(cwd = process.cwd()) {
87367
87550
  if (!(0, import_node_fs55.existsSync)(manifestPath)) continue;
87368
87551
  const manifest = readManifestAt(manifestPath);
87369
87552
  if (!manifest?.version) continue;
87370
- if (manifest.version !== CLI_VERSION) {
87553
+ const cmp = compareSemver(manifest.version, CLI_VERSION);
87554
+ if (cmp !== null && cmp < 0) {
87371
87555
  stale.push(`${target.label} (v${manifest.version})`);
87372
87556
  }
87373
87557
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archal",
3
- "version": "0.9.11",
3
+ "version": "0.9.12",
4
4
  "description": "Test your agents & integrations against digital twins",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -45,8 +45,13 @@
45
45
  "twin-assets",
46
46
  "LICENSE"
47
47
  ],
48
- "dependencies": {
49
- "vitest": "^2.1.0"
48
+ "peerDependencies": {
49
+ "vitest": ">=2.1.0"
50
+ },
51
+ "peerDependenciesMeta": {
52
+ "vitest": {
53
+ "optional": true
54
+ }
50
55
  },
51
56
  "scripts": {
52
57
  "verify:artifacts": "node scripts/assert-artifacts.mjs",
@@ -8,6 +8,19 @@ user-invocable: true
8
8
 
9
9
  You are setting up Archal in this project. Archal tests AI agents against digital twins of real services (GitHub, Slack, Stripe, etc.). Handle installation and auth yourself; delegate the workflow-specific setup to the matching sub-skill.
10
10
 
11
+ ## If this is a cold-start
12
+
13
+ The user may have landed here without running `npx archal init` first. If the
14
+ CLI is missing (see "Install + auth" below) AND no `.archal-manifest.json`
15
+ exists in `.claude/skills/`, the canonical first command is:
16
+
17
+ ```bash
18
+ npx archal init
19
+ ```
20
+
21
+ That adds `archal` as a devDependency and reinstalls these skills at the
22
+ right version. Re-invoke the onboard skill after it completes.
23
+
11
24
  ## Discover first
12
25
 
13
26
  Before asking anything, read the repo:
@@ -25,9 +38,12 @@ Before asking anything, read the repo:
25
38
 
26
39
  ## Install + auth
27
40
 
41
+ If you're here because the user ran `npx archal init`, the CLI is already
42
+ a devDependency and the skills are in place. Skip to `archal login`.
43
+
28
44
  ```bash
29
- npx archal --version # check if installed
30
- npm install -D archal # install if not (or -g for global)
45
+ npx archal --version # verify CLI is on PATH / in node_modules
46
+ npx archal init --skills-only # re-stage skills if they drifted
31
47
  archal usage # check auth
32
48
  archal login # OAuth browser flow, or: archal login --token <token>
33
49
  ```