archal 0.9.11 → 0.9.13

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,78 @@ 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 findPnpmWorkspaceRoot(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
+ }
87294
+ function findRepoRoot(start) {
87295
+ let dir = start;
87296
+ for (let i = 0; i < 10; i++) {
87297
+ if ((0, import_node_fs53.existsSync)((0, import_node_path53.join)(dir, "pnpm-workspace.yaml"))) return dir;
87298
+ const parent = (0, import_node_path53.dirname)(dir);
87299
+ if (parent === dir) return null;
87300
+ dir = parent;
87301
+ }
87302
+ return null;
87303
+ }
87174
87304
  function resolveSkillsDir() {
87175
87305
  const here = typeof __dirname === "string" ? __dirname : process.cwd();
87306
+ const repoRoot = findRepoRoot(here) ?? findRepoRoot(process.cwd());
87176
87307
  const candidates = [
87177
87308
  (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")
87309
+ ...repoRoot ? [(0, import_node_path53.resolve)(repoRoot, "packages", "archal", "skills")] : [],
87310
+ (0, import_node_path53.resolve)(process.cwd(), "packages", "archal", "skills"),
87311
+ (0, import_node_path53.resolve)(process.cwd(), "skills")
87180
87312
  ];
87181
87313
  for (const candidate of candidates) {
87182
- if ((0, import_node_fs53.existsSync)(candidate)) return candidate;
87314
+ if (looksLikeSkillsDir(candidate)) return candidate;
87183
87315
  }
87184
87316
  throw new CliRuntimeError(
87185
- "Could not locate the Archal skills directory. Reinstall archal, or run `npx @archal/skills` directly."
87317
+ "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
87318
  );
87187
87319
  }
87188
87320
  function runPmAdd(pm, cwd, spec) {
87189
- const args = pm === "npm" ? ["install", "--save-dev", spec] : ["add", "-D", spec];
87321
+ let args;
87322
+ if (pm === "npm") {
87323
+ args = ["install", "--save-dev", "--no-audit", "--no-fund", "--loglevel=error", spec];
87324
+ } else if (pm === "pnpm" && findPnpmWorkspaceRoot(cwd) === cwd) {
87325
+ args = ["add", "-D", "-w", spec];
87326
+ } else {
87327
+ args = ["add", "-D", spec];
87328
+ }
87190
87329
  const result = (0, import_node_child_process10.spawnSync)(pm, args, { cwd, stdio: "inherit" });
87330
+ if (result.error) {
87331
+ throw new CliRuntimeError(
87332
+ `Failed to launch ${pm}: ${result.error.message}. Install ${pm} (or install \`archal\` manually) and re-run \`archal init\`.`
87333
+ );
87334
+ }
87191
87335
  if (result.status !== 0) {
87192
87336
  throw new CliRuntimeError(
87193
- `Failed to install ${spec} with ${pm}. Install manually and re-run \`archal init\`.`
87337
+ `Failed to install ${spec} with ${pm} (exit ${result.status}). Install manually and re-run \`archal init\`.`
87194
87338
  );
87195
87339
  }
87196
87340
  }
@@ -87209,29 +87353,71 @@ function createInitCommand() {
87209
87353
  throw new CliRuntimeError(err instanceof Error ? err.message : String(err));
87210
87354
  }
87211
87355
  }
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.
87356
+ let cliInstalled = true;
87357
+ const hadPackageJson = hasPackageJson(cwd);
87358
+ const startedAt = Date.now();
87359
+ try {
87360
+ if (!opts.skillsOnly) {
87361
+ if (!SEMVER_RE.test(CLI_VERSION)) {
87362
+ throw new CliRuntimeError(
87363
+ `Invalid CLI_VERSION "${CLI_VERSION}" \u2014 refusing to install. Reinstall archal and retry.`
87364
+ );
87365
+ }
87366
+ if (!hadPackageJson) {
87367
+ process.stderr.write(
87368
+ "No package.json in cwd \u2014 skipping devDependency install. Install archal globally (`npm i -g archal`) or initialize a package.json first.\n"
87369
+ );
87370
+ cliInstalled = false;
87371
+ } else if (alreadyHasArchalDep(cwd)) {
87372
+ process.stdout.write(`archal already a dependency \u2014 skipping install.
87219
87373
  `);
87220
- } else {
87221
- const pm = detectPackageManager2(cwd);
87222
- process.stdout.write(`Installing archal@${CLI_VERSION} with ${pm}\u2026
87374
+ } else {
87375
+ const pm = detectPackageManager2(cwd);
87376
+ process.stdout.write(`Installing archal@${CLI_VERSION} with ${pm}\u2026
87223
87377
  `);
87224
- runPmAdd(pm, cwd, `archal@${CLI_VERSION}`);
87378
+ runPmAdd(pm, cwd, `archal@${CLI_VERSION}`);
87379
+ }
87380
+ } else {
87381
+ cliInstalled = alreadyHasArchalDep(cwd);
87382
+ }
87383
+ const result = await runInstaller({
87384
+ cwd,
87385
+ targets: targets ?? void 0,
87386
+ nonInteractive: opts.nonInteractive === true,
87387
+ yes: opts.yes === true,
87388
+ skillsDir: resolveSkillsDir(),
87389
+ version: CLI_VERSION
87390
+ });
87391
+ if (opts.skillsOnly && !cliInstalled) {
87392
+ process.stdout.write(
87393
+ `Note: --skills-only left out the archal runner. Run \`npm install -D archal\` (or pnpm / yarn / bun equivalent) before \`archal run\`.
87394
+ `
87395
+ );
87225
87396
  }
87397
+ captureCliEvent("cli_init_completed", {
87398
+ skills_only: Boolean(opts.skillsOnly),
87399
+ non_interactive: opts.nonInteractive === true,
87400
+ yes: opts.yes === true,
87401
+ target_count: result.targets.length,
87402
+ targets: result.targets,
87403
+ installed: result.installed,
87404
+ skipped: result.skipped.length,
87405
+ had_package_json: hadPackageJson,
87406
+ cli_installed: cliInstalled,
87407
+ duration_ms: Date.now() - startedAt
87408
+ });
87409
+ } catch (err) {
87410
+ captureCliEvent("cli_init_failed", {
87411
+ skills_only: Boolean(opts.skillsOnly),
87412
+ non_interactive: opts.nonInteractive === true,
87413
+ yes: opts.yes === true,
87414
+ had_package_json: hadPackageJson,
87415
+ error_name: err instanceof Error ? err.constructor.name : "Unknown",
87416
+ error_message: err instanceof Error ? err.message : String(err),
87417
+ duration_ms: Date.now() - startedAt
87418
+ });
87419
+ throw err;
87226
87420
  }
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
87421
  }
87236
87422
  );
87237
87423
  }
@@ -87359,6 +87545,20 @@ function readManifestAt(path2) {
87359
87545
  return null;
87360
87546
  }
87361
87547
  }
87548
+ function compareSemver(a, b) {
87549
+ const parse3 = (v) => {
87550
+ const m = /^(\d+)\.(\d+)\.(\d+)(?:[-+][0-9A-Za-z.-]+)?$/.exec(v);
87551
+ if (!m) return null;
87552
+ return [Number(m[1]), Number(m[2]), Number(m[3])];
87553
+ };
87554
+ const pa = parse3(a);
87555
+ const pb = parse3(b);
87556
+ if (!pa || !pb) return null;
87557
+ if (pa[0] !== pb[0]) return pa[0] - pb[0];
87558
+ if (pa[1] !== pb[1]) return pa[1] - pb[1];
87559
+ if (pa[2] !== pb[2]) return pa[2] - pb[2];
87560
+ return 0;
87561
+ }
87362
87562
  function maybePrintSkillDriftBanner(cwd = process.cwd()) {
87363
87563
  try {
87364
87564
  const stale = [];
@@ -87367,7 +87567,8 @@ function maybePrintSkillDriftBanner(cwd = process.cwd()) {
87367
87567
  if (!(0, import_node_fs55.existsSync)(manifestPath)) continue;
87368
87568
  const manifest = readManifestAt(manifestPath);
87369
87569
  if (!manifest?.version) continue;
87370
- if (manifest.version !== CLI_VERSION) {
87570
+ const cmp = compareSemver(manifest.version, CLI_VERSION);
87571
+ if (cmp !== null && cmp < 0) {
87371
87572
  stale.push(`${target.label} (v${manifest.version})`);
87372
87573
  }
87373
87574
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archal",
3
- "version": "0.9.11",
3
+ "version": "0.9.13",
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,14 +38,23 @@ Before asking anything, read the repo:
25
38
 
26
39
  ## Install + auth
27
40
 
41
+ If you're here via `npx archal init`, archal is already a devDependency
42
+ and the skills are already in place. Go straight to login:
43
+
28
44
  ```bash
29
- npx archal --version # check if installed
30
- npm install -D archal # install if not (or -g for global)
31
- archal usage # check auth
32
45
  archal login # OAuth browser flow, or: archal login --token <token>
46
+ archal usage # verify auth + plan
33
47
  ```
34
48
 
35
- In CI, use `ARCHAL_TOKEN` instead of `archal login`.
49
+ In CI, set `ARCHAL_TOKEN` instead of running `archal login`.
50
+
51
+ If something feels wrong (missing CLI, stale skills), these are the
52
+ recovery commands — don't run them otherwise:
53
+
54
+ ```bash
55
+ npx archal --version # CLI reachable? prints e.g. 0.9.12
56
+ npx archal init --skills-only # re-stage skills if they drifted
57
+ ```
36
58
 
37
59
  ## Pick a workflow
38
60
 
@@ -99,6 +99,8 @@ Aliases for `evaluator-model`: `evaluator`, `evaluatormodel`, `model`.
99
99
  | `supabase` | `empty`, `small-project`, `saas-starter`, `ecommerce` |
100
100
  | `google-workspace` | `empty`, `assistant-baseline`, `gmail-busy-inbox`, `calendar-packed-week` |
101
101
  | `ramp` | `empty`, `default` |
102
+ | `discord` | `empty`, `small-server`, `harvested` |
103
+ | `telegram` | `empty`, `harvested` |
102
104
 
103
105
  ## Twin auto-detection from content
104
106
 
@@ -111,6 +113,12 @@ If no `twins:` config is set, Archal infers twins from keywords in Setup, Expect
111
113
  - `stripe`, `payment`, `refund`, `subscription`, `invoice` -> `stripe`
112
114
  - `supabase`, `database`, `sql query` -> `supabase`
113
115
  - `google workspace`, `gmail`, `calendar event`, `inbox` -> `google-workspace`
116
+ - `discord`, `guild`, `text channel` -> `discord`
117
+
118
+ Not every twin has auto-detect keywords — `telegram` in particular has
119
+ none. If your scenario uses `telegram`, set `twins: telegram` in the
120
+ Config block or in `.archal.json`. `ramp` auto-detects on `ramp`,
121
+ `bill`, `expense`, `reimbursement`, `fund`, `card spend`.
114
122
 
115
123
  ## Multi-service scenarios
116
124
 
@@ -13,7 +13,8 @@
13
13
  },
14
14
  "refresh_token": {
15
15
  "type": "string",
16
- "minLength": 1
16
+ "minLength": 1,
17
+ "maxLength": 2048
17
18
  },
18
19
  "client_id": {
19
20
  "type": "string"