@cleocode/caamp 2026.9.19 → 2026.9.21

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/cli.js CHANGED
@@ -1,36 +1,42 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ ErrorCategories,
4
+ ErrorCodes,
3
5
  MarketplaceClient,
4
6
  PiHarness,
5
7
  RECOMMENDATION_ERROR_CODES,
8
+ SkillInstallError,
9
+ buildEnvelope,
6
10
  checkSkillUpdate,
11
+ cloneGitLabRepo,
12
+ cloneRepo,
7
13
  detectAllProviders,
8
14
  detectMcpInstallations,
9
15
  detectProjectProviders,
10
- discoverSkill,
11
16
  discoverSkillsMulti,
12
- dispatchInstallSkillAcrossProviders,
13
17
  dispatchRemoveSkillAcrossProviders,
18
+ emitError,
19
+ emitJsonError,
14
20
  fetchWithTimeout,
15
21
  formatNetworkError,
16
22
  formatSkillRecommendations,
17
23
  getHarnessFor,
18
24
  getInstalledProviders,
19
- getSkill,
20
25
  getSkillDir,
21
26
  getTrackedSkills,
27
+ handleFormatError,
22
28
  installBatchWithRollback,
23
29
  installMcpServer,
30
+ installResolvedSkill,
31
+ installSkillFromSource,
24
32
  isCatalogAvailable,
25
33
  isHuman,
26
- isMarketplaceScoped,
27
- isQuiet,
28
34
  isVerbose,
29
35
  listAllMcpServers,
30
36
  listCanonicalSkills,
31
37
  listMcpServers,
32
38
  listProfiles,
33
- listSkills,
39
+ outputSuccess,
34
40
  parseSource,
35
41
  readConfig,
36
42
  readLockFile,
@@ -40,6 +46,7 @@ import {
40
46
  removeMcpServerFromAll,
41
47
  removeSkillFromLock,
42
48
  resolveDefaultTargetProviders,
49
+ resolveFormat,
43
50
  resolveProfile,
44
51
  scanDirectory,
45
52
  scanFile,
@@ -52,7 +59,7 @@ import {
52
59
  tokenizeCriteriaValue,
53
60
  updateInstructionsSingleOperation,
54
61
  validateSkill
55
- } from "./chunk-IBHW6GEI.js";
62
+ } from "./chunk-UYVHBBJE.js";
56
63
  import {
57
64
  buildSkillsMap,
58
65
  checkAllInjections,
@@ -65,7 +72,7 @@ import {
65
72
  groupByInstructFile,
66
73
  injectAll,
67
74
  providerSupports
68
- } from "./chunk-CHPNPLOK.js";
75
+ } from "./chunk-UIXTFV35.js";
69
76
  import {
70
77
  CANONICAL_HOOK_EVENTS,
71
78
  buildHookMatrix,
@@ -76,7 +83,6 @@ import {
76
83
  translateToAll
77
84
  } from "./chunk-YCAANESR.js";
78
85
  import {
79
- buildSkillSubPathCandidates,
80
86
  resolveProviderConfigPath,
81
87
  resolveProviderSkillsDir
82
88
  } from "./chunk-HDZYZTR2.js";
@@ -153,7 +159,7 @@ function emitSuccess(operation, result, mvi = "standard") {
153
159
  };
154
160
  console.log(JSON.stringify(envelope, null, 2));
155
161
  }
156
- function emitError(operation, error, mvi = "standard") {
162
+ function emitError2(operation, error, mvi = "standard") {
157
163
  let envelope;
158
164
  if (error instanceof LAFSCommandError) {
159
165
  envelope = {
@@ -204,7 +210,7 @@ async function runLafsCommand(command, mvi, action) {
204
210
  const result = await action();
205
211
  emitSuccess(command, result, mvi);
206
212
  } catch (error) {
207
- emitError(command, error, mvi);
213
+ emitError2(command, error, mvi);
208
214
  process.exit(1);
209
215
  }
210
216
  }
@@ -499,121 +505,6 @@ function registerAdvancedCommands(program2) {
499
505
  // src/commands/config.ts
500
506
  import { existsSync } from "fs";
501
507
  import pc from "picocolors";
502
-
503
- // src/core/lafs.ts
504
- import { randomUUID as randomUUID2 } from "crypto";
505
- import { resolveOutputFormat } from "@cleocode/lafs";
506
- function resolveFormat(options) {
507
- return resolveOutputFormat({
508
- jsonFlag: options.jsonFlag ?? false,
509
- humanFlag: (options.humanFlag ?? false) || isHuman(),
510
- projectDefault: options.projectDefault ?? "json"
511
- }).format;
512
- }
513
- function buildEnvelope(operation, mvi, result, error, page = null, sessionId, warnings) {
514
- return {
515
- $schema: "https://lafs.dev/schemas/v1/envelope.schema.json",
516
- _meta: {
517
- specVersion: "1.0.0",
518
- schemaVersion: "1.0.0",
519
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
520
- operation,
521
- requestId: randomUUID2(),
522
- transport: "cli",
523
- strict: true,
524
- mvi,
525
- contextVersion: 0,
526
- ...sessionId && { sessionId },
527
- ...warnings && warnings.length > 0 && { warnings }
528
- },
529
- success: error === null,
530
- result,
531
- error,
532
- page
533
- };
534
- }
535
- function emitError2(operation, mvi, code, message, category, details = {}, exitCode = 1) {
536
- const envelope = buildEnvelope(operation, mvi, null, {
537
- code,
538
- message,
539
- category,
540
- retryable: category === "TRANSIENT" || category === "RATE_LIMIT",
541
- retryAfterMs: null,
542
- details
543
- });
544
- console.error(JSON.stringify(envelope, null, 2));
545
- process.exit(exitCode);
546
- }
547
- function emitJsonError(operation, mvi, code, message, category, details = {}) {
548
- const envelope = buildEnvelope(operation, mvi, null, {
549
- code,
550
- message,
551
- category,
552
- retryable: category === "TRANSIENT" || category === "RATE_LIMIT",
553
- retryAfterMs: null,
554
- details
555
- });
556
- console.error(JSON.stringify(envelope, null, 2));
557
- }
558
- function outputSuccess(operation, mvi, result, page, sessionId, warnings) {
559
- const envelope = buildEnvelope(operation, mvi, result, null, page ?? null, sessionId, warnings);
560
- if (isQuiet() && !envelope.error) {
561
- return;
562
- }
563
- console.log(JSON.stringify(envelope, null, 2));
564
- }
565
- function handleFormatError(error, operation, mvi, jsonFlag) {
566
- const message = error instanceof Error ? error.message : String(error);
567
- if (jsonFlag) {
568
- emitJsonError(operation, mvi, "E_FORMAT_CONFLICT", message, "VALIDATION");
569
- } else {
570
- console.error(message);
571
- }
572
- process.exit(1);
573
- }
574
- var ErrorCategories = {
575
- VALIDATION: "VALIDATION",
576
- AUTH: "AUTH",
577
- PERMISSION: "PERMISSION",
578
- NOT_FOUND: "NOT_FOUND",
579
- CONFLICT: "CONFLICT",
580
- RATE_LIMIT: "RATE_LIMIT",
581
- TRANSIENT: "TRANSIENT",
582
- INTERNAL: "INTERNAL",
583
- CONTRACT: "CONTRACT",
584
- MIGRATION: "MIGRATION"
585
- };
586
- var ErrorCodes = {
587
- // Format errors
588
- FORMAT_CONFLICT: "E_FORMAT_CONFLICT",
589
- INVALID_JSON: "E_INVALID_JSON",
590
- // Not found errors
591
- SKILL_NOT_FOUND: "E_SKILL_NOT_FOUND",
592
- PROVIDER_NOT_FOUND: "E_PROVIDER_NOT_FOUND",
593
- MCP_SERVER_NOT_FOUND: "E_MCP_SERVER_NOT_FOUND",
594
- FILE_NOT_FOUND: "E_FILE_NOT_FOUND",
595
- // Validation errors
596
- INVALID_INPUT: "E_INVALID_INPUT",
597
- INVALID_CONSTRAINT: "E_INVALID_CONSTRAINT",
598
- INVALID_FORMAT: "E_INVALID_FORMAT",
599
- // Operation errors
600
- INSTALL_FAILED: "E_INSTALL_FAILED",
601
- // Skills federation gates (T9564 — Sphere B)
602
- SKILL_TRUST_GATE_BLOCKED: "E_SKILL_TRUST_GATE_BLOCKED",
603
- FEDERATION_UNKNOWN_SOURCE_INTERACTIVE_REQUIRED: "E_FEDERATION_UNKNOWN_SOURCE_INTERACTIVE_REQUIRED",
604
- FEDERATION_CHECKSUM_MISMATCH: "E_FEDERATION_CHECKSUM_MISMATCH",
605
- REMOVE_FAILED: "E_REMOVE_FAILED",
606
- UPDATE_FAILED: "E_UPDATE_FAILED",
607
- VALIDATION_FAILED: "E_VALIDATION_FAILED",
608
- AUDIT_FAILED: "E_AUDIT_FAILED",
609
- // System errors
610
- NETWORK_ERROR: "E_NETWORK_ERROR",
611
- FILE_SYSTEM_ERROR: "E_FILE_SYSTEM_ERROR",
612
- PERMISSION_DENIED: "E_PERMISSION_DENIED",
613
- INTERNAL_ERROR: "E_INTERNAL_ERROR"
614
- };
615
-
616
- // src/commands/config.ts
617
508
  function registerConfigCommand(program2) {
618
509
  const config = program2.command("config").description("View provider configuration");
619
510
  config.command("show").description("Show provider configuration").argument("<provider>", "Provider ID or alias").option("-g, --global", "Show global config").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format").action(
@@ -1950,60 +1841,9 @@ function registerMcpCommands(program2) {
1950
1841
  // src/commands/pi/cant.ts
1951
1842
  import { existsSync as existsSync4 } from "fs";
1952
1843
  import { writeFile } from "fs/promises";
1953
- import { tmpdir as tmpdir3 } from "os";
1954
- import { isAbsolute, join as join5 } from "path";
1955
-
1956
- // src/core/sources/github.ts
1957
- import { mkdtemp, rm } from "fs/promises";
1958
1844
  import { tmpdir } from "os";
1959
- import { join as join3 } from "path";
1960
- import { simpleGit } from "simple-git";
1961
- async function cloneRepo(owner, repo, ref, subPath) {
1962
- const tmpDir = await mkdtemp(join3(tmpdir(), "caamp-"));
1963
- const repoUrl = `https://github.com/${owner}/${repo}.git`;
1964
- const git = simpleGit();
1965
- const cloneOptions = ["--depth", "1"];
1966
- if (ref) {
1967
- cloneOptions.push("--branch", ref);
1968
- }
1969
- await git.clone(repoUrl, tmpDir, cloneOptions);
1970
- const localPath = subPath ? join3(tmpDir, subPath) : tmpDir;
1971
- return {
1972
- localPath,
1973
- cleanup: async () => {
1974
- try {
1975
- await rm(tmpDir, { recursive: true });
1976
- } catch {
1977
- }
1978
- }
1979
- };
1980
- }
1981
-
1982
- // src/core/sources/gitlab.ts
1983
- import { mkdtemp as mkdtemp2, rm as rm2 } from "fs/promises";
1984
- import { tmpdir as tmpdir2 } from "os";
1985
- import { join as join4 } from "path";
1986
- import { simpleGit as simpleGit2 } from "simple-git";
1987
- async function cloneGitLabRepo(owner, repo, ref, subPath) {
1988
- const tmpDir = await mkdtemp2(join4(tmpdir2(), "caamp-gl-"));
1989
- const repoUrl = `https://gitlab.com/${owner}/${repo}.git`;
1990
- const git = simpleGit2();
1991
- const cloneOptions = ["--depth", "1"];
1992
- if (ref) {
1993
- cloneOptions.push("--branch", ref);
1994
- }
1995
- await git.clone(repoUrl, tmpDir, cloneOptions);
1996
- const localPath = subPath ? join4(tmpDir, subPath) : tmpDir;
1997
- return {
1998
- localPath,
1999
- cleanup: async () => {
2000
- try {
2001
- await rm2(tmpDir, { recursive: true });
2002
- } catch {
2003
- }
2004
- }
2005
- };
2006
- }
1845
+ import { isAbsolute, join as join3 } from "path";
1846
+ import { expandTildePath } from "@cleocode/paths";
2007
1847
 
2008
1848
  // src/commands/pi/common.ts
2009
1849
  var PI_ERROR_CODES = {
@@ -2066,7 +1906,7 @@ function resolveProjectDir2(tier, explicit) {
2066
1906
  // src/commands/pi/cant.ts
2067
1907
  async function resolveCantSource(source) {
2068
1908
  if (isAbsolute(source) || source.startsWith("./") || source.startsWith("../") || source.startsWith("~")) {
2069
- const expanded = source.startsWith("~/") ? join5(process.env["HOME"] ?? "", source.slice(2)) : source;
1909
+ const expanded = expandTildePath(source);
2070
1910
  if (!existsSync4(expanded)) {
2071
1911
  throw new LAFSCommandError(
2072
1912
  PI_ERROR_CODES.NOT_FOUND,
@@ -2086,7 +1926,7 @@ async function resolveCantSource(source) {
2086
1926
  const parsed2 = parseSource(source);
2087
1927
  if (parsed2.type === "github" && parsed2.owner !== void 0 && parsed2.repo !== void 0) {
2088
1928
  const cloneResult = await cloneRepo(parsed2.owner, parsed2.repo, parsed2.ref);
2089
- const filePath = parsed2.path !== void 0 ? join5(cloneResult.localPath, parsed2.path) : cloneResult.localPath;
1929
+ const filePath = parsed2.path !== void 0 ? join3(cloneResult.localPath, parsed2.path) : cloneResult.localPath;
2090
1930
  if (!existsSync4(filePath)) {
2091
1931
  await cloneResult.cleanup();
2092
1932
  throw new LAFSCommandError(
@@ -2104,7 +1944,7 @@ async function resolveCantSource(source) {
2104
1944
  }
2105
1945
  if (parsed2.type === "gitlab" && parsed2.owner !== void 0 && parsed2.repo !== void 0) {
2106
1946
  const cloneResult = await cloneGitLabRepo(parsed2.owner, parsed2.repo, parsed2.ref);
2107
- const filePath = parsed2.path !== void 0 ? join5(cloneResult.localPath, parsed2.path) : cloneResult.localPath;
1947
+ const filePath = parsed2.path !== void 0 ? join3(cloneResult.localPath, parsed2.path) : cloneResult.localPath;
2108
1948
  if (!existsSync4(filePath)) {
2109
1949
  await cloneResult.cleanup();
2110
1950
  throw new LAFSCommandError(
@@ -2131,7 +1971,7 @@ async function resolveCantSource(source) {
2131
1971
  }
2132
1972
  const body = await resp.text();
2133
1973
  const baseName = inferNameFromUrl(source);
2134
- const tmp = join5(tmpdir3(), `caamp-pi-cant-${process.pid}-${Date.now()}-${baseName}.cant`);
1974
+ const tmp = join3(tmpdir(), `caamp-pi-cant-${process.pid}-${Date.now()}-${baseName}.cant`);
2135
1975
  await writeFile(tmp, body, "utf8");
2136
1976
  return {
2137
1977
  localPath: tmp,
@@ -2147,7 +1987,7 @@ async function resolveCantSource(source) {
2147
1987
  const parsed = parseSource(source);
2148
1988
  if (parsed.type === "github" && parsed.owner !== void 0 && parsed.repo !== void 0) {
2149
1989
  const cloneResult = await cloneRepo(parsed.owner, parsed.repo, parsed.ref);
2150
- const filePath = parsed.path !== void 0 ? join5(cloneResult.localPath, parsed.path) : cloneResult.localPath;
1990
+ const filePath = parsed.path !== void 0 ? join3(cloneResult.localPath, parsed.path) : cloneResult.localPath;
2151
1991
  if (!existsSync4(filePath)) {
2152
1992
  await cloneResult.cleanup();
2153
1993
  throw new LAFSCommandError(
@@ -2311,11 +2151,12 @@ function registerPiCantCommands(parent) {
2311
2151
  // src/commands/pi/extensions.ts
2312
2152
  import { existsSync as existsSync5 } from "fs";
2313
2153
  import { writeFile as writeFile2 } from "fs/promises";
2314
- import { tmpdir as tmpdir4 } from "os";
2315
- import { isAbsolute as isAbsolute2, join as join6 } from "path";
2154
+ import { tmpdir as tmpdir2 } from "os";
2155
+ import { isAbsolute as isAbsolute2, join as join4 } from "path";
2156
+ import { expandTildePath as expandTildePath2 } from "@cleocode/paths";
2316
2157
  async function resolveExtensionSource(source) {
2317
2158
  if (isAbsolute2(source) || source.startsWith("./") || source.startsWith("../") || source.startsWith("~")) {
2318
- const expanded = source.startsWith("~/") ? join6(process.env["HOME"] ?? "", source.slice(2)) : source;
2159
+ const expanded = expandTildePath2(source);
2319
2160
  if (!existsSync5(expanded)) {
2320
2161
  throw new LAFSCommandError(
2321
2162
  PI_ERROR_CODES.NOT_FOUND,
@@ -2336,7 +2177,7 @@ async function resolveExtensionSource(source) {
2336
2177
  const parsed2 = parseSource(source);
2337
2178
  if (parsed2.type === "github" && parsed2.owner !== void 0 && parsed2.repo !== void 0) {
2338
2179
  const cloneResult = await cloneRepo(parsed2.owner, parsed2.repo, parsed2.ref);
2339
- const filePath = parsed2.path !== void 0 ? join6(cloneResult.localPath, parsed2.path) : cloneResult.localPath;
2180
+ const filePath = parsed2.path !== void 0 ? join4(cloneResult.localPath, parsed2.path) : cloneResult.localPath;
2340
2181
  if (!existsSync5(filePath)) {
2341
2182
  await cloneResult.cleanup();
2342
2183
  throw new LAFSCommandError(
@@ -2354,7 +2195,7 @@ async function resolveExtensionSource(source) {
2354
2195
  }
2355
2196
  if (parsed2.type === "gitlab" && parsed2.owner !== void 0 && parsed2.repo !== void 0) {
2356
2197
  const cloneResult = await cloneGitLabRepo(parsed2.owner, parsed2.repo, parsed2.ref);
2357
- const filePath = parsed2.path !== void 0 ? join6(cloneResult.localPath, parsed2.path) : cloneResult.localPath;
2198
+ const filePath = parsed2.path !== void 0 ? join4(cloneResult.localPath, parsed2.path) : cloneResult.localPath;
2358
2199
  if (!existsSync5(filePath)) {
2359
2200
  await cloneResult.cleanup();
2360
2201
  throw new LAFSCommandError(
@@ -2381,7 +2222,7 @@ async function resolveExtensionSource(source) {
2381
2222
  }
2382
2223
  const body = await resp.text();
2383
2224
  const baseName = inferNameFromUrl2(source);
2384
- const tmp = join6(tmpdir4(), `caamp-pi-ext-${process.pid}-${Date.now()}-${baseName}.ts`);
2225
+ const tmp = join4(tmpdir2(), `caamp-pi-ext-${process.pid}-${Date.now()}-${baseName}.ts`);
2385
2226
  await writeFile2(tmp, body, "utf8");
2386
2227
  return {
2387
2228
  localPath: tmp,
@@ -2397,7 +2238,7 @@ async function resolveExtensionSource(source) {
2397
2238
  const parsed = parseSource(source);
2398
2239
  if (parsed.type === "github" && parsed.owner !== void 0 && parsed.repo !== void 0) {
2399
2240
  const cloneResult = await cloneRepo(parsed.owner, parsed.repo, parsed.ref);
2400
- const filePath = parsed.path !== void 0 ? join6(cloneResult.localPath, parsed.path) : cloneResult.localPath;
2241
+ const filePath = parsed.path !== void 0 ? join4(cloneResult.localPath, parsed.path) : cloneResult.localPath;
2401
2242
  if (!existsSync5(filePath)) {
2402
2243
  await cloneResult.cleanup();
2403
2244
  throw new LAFSCommandError(
@@ -2666,7 +2507,7 @@ function registerPiModelsCommands(parent) {
2666
2507
 
2667
2508
  // src/commands/pi/prompts.ts
2668
2509
  import { existsSync as existsSync6 } from "fs";
2669
- import { join as join7, resolve } from "path";
2510
+ import { join as join5, resolve } from "path";
2670
2511
  function inferPromptName(sourceDir) {
2671
2512
  const normalized = resolve(sourceDir).replace(/[\\/]+$/, "");
2672
2513
  const base = normalized.split(/[\\/]/).pop();
@@ -2696,7 +2537,7 @@ function registerPiPromptsCommands(parent) {
2696
2537
  false
2697
2538
  );
2698
2539
  }
2699
- if (!existsSync6(join7(absSource, "prompt.md"))) {
2540
+ if (!existsSync6(join5(absSource, "prompt.md"))) {
2700
2541
  throw new LAFSCommandError(
2701
2542
  PI_ERROR_CODES.VALIDATION,
2702
2543
  `Source directory is missing prompt.md: ${absSource}`,
@@ -3013,8 +2854,8 @@ function registerPiCommands(program2) {
3013
2854
  }
3014
2855
 
3015
2856
  // src/commands/providers.ts
3016
- import { randomUUID as randomUUID3 } from "crypto";
3017
- import { resolveOutputFormat as resolveOutputFormat2 } from "@cleocode/lafs";
2857
+ import { randomUUID as randomUUID2 } from "crypto";
2858
+ import { resolveOutputFormat } from "@cleocode/lafs";
3018
2859
  import pc6 from "picocolors";
3019
2860
  function registerProvidersCommand(program2) {
3020
2861
  const providers = program2.command("providers").description("Manage AI agent providers");
@@ -3023,7 +2864,7 @@ function registerProvidersCommand(program2) {
3023
2864
  const mvi = "standard";
3024
2865
  let format;
3025
2866
  try {
3026
- format = resolveOutputFormat2({
2867
+ format = resolveOutputFormat({
3027
2868
  jsonFlag: opts.json ?? false,
3028
2869
  humanFlag: (opts.human ?? false) || isHuman(),
3029
2870
  projectDefault: "json"
@@ -3073,7 +2914,7 @@ CAMP Provider Registry v${getRegistryVersion()}`));
3073
2914
  const mvi = "standard";
3074
2915
  let format;
3075
2916
  try {
3076
- format = resolveOutputFormat2({
2917
+ format = resolveOutputFormat({
3077
2918
  jsonFlag: opts.json ?? false,
3078
2919
  humanFlag: (opts.human ?? false) || isHuman(),
3079
2920
  projectDefault: "json"
@@ -3129,7 +2970,7 @@ Detected ${installed.length} installed providers:
3129
2970
  const mvi = "standard";
3130
2971
  let format;
3131
2972
  try {
3132
- format = resolveOutputFormat2({
2973
+ format = resolveOutputFormat({
3133
2974
  jsonFlag: opts.json ?? false,
3134
2975
  humanFlag: (opts.human ?? false) || isHuman(),
3135
2976
  projectDefault: "json"
@@ -3202,7 +3043,7 @@ ${provider.toolName}`));
3202
3043
  const mvi = "standard";
3203
3044
  let format;
3204
3045
  try {
3205
- format = resolveOutputFormat2({
3046
+ format = resolveOutputFormat({
3206
3047
  jsonFlag: opts.json ?? false,
3207
3048
  humanFlag: (opts.human ?? false) || isHuman(),
3208
3049
  projectDefault: "json"
@@ -3260,7 +3101,7 @@ ${provider.toolName}`));
3260
3101
  const mvi = "standard";
3261
3102
  let format;
3262
3103
  try {
3263
- format = resolveOutputFormat2({
3104
+ format = resolveOutputFormat({
3264
3105
  jsonFlag: opts.json ?? false,
3265
3106
  humanFlag: (opts.human ?? false) || isHuman(),
3266
3107
  projectDefault: "json"
@@ -3328,7 +3169,7 @@ CAMP Hook Support (mappings v${getHookMappingsVersion()})
3328
3169
  const mvi = "standard";
3329
3170
  let format;
3330
3171
  try {
3331
- format = resolveOutputFormat2({
3172
+ format = resolveOutputFormat({
3332
3173
  jsonFlag: opts.json ?? false,
3333
3174
  humanFlag: (opts.human ?? false) || isHuman(),
3334
3175
  projectDefault: "json"
@@ -3375,7 +3216,7 @@ CAMP Hook Support (mappings v${getHookMappingsVersion()})
3375
3216
  const mvi = "standard";
3376
3217
  let format;
3377
3218
  try {
3378
- format = resolveOutputFormat2({
3219
+ format = resolveOutputFormat({
3379
3220
  jsonFlag: opts.json ?? false,
3380
3221
  humanFlag: (opts.human ?? false) || isHuman(),
3381
3222
  projectDefault: "json"
@@ -3506,7 +3347,7 @@ CAMP Hook Support (mappings v${getHookMappingsVersion()})
3506
3347
  const mvi = "standard";
3507
3348
  let format;
3508
3349
  try {
3509
- format = resolveOutputFormat2({
3350
+ format = resolveOutputFormat({
3510
3351
  jsonFlag: opts.json ?? false,
3511
3352
  humanFlag: (opts.human ?? false) || isHuman(),
3512
3353
  projectDefault: "json"
@@ -3576,7 +3417,7 @@ function buildEnvelope2(operation, mvi, result, error) {
3576
3417
  schemaVersion: "1.0.0",
3577
3418
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3578
3419
  operation,
3579
- requestId: randomUUID3(),
3420
+ requestId: randomUUID2(),
3580
3421
  transport: "cli",
3581
3422
  strict: true,
3582
3423
  mvi,
@@ -3892,8 +3733,8 @@ function registerSkillsCheck(parent) {
3892
3733
  }
3893
3734
 
3894
3735
  // src/commands/skills/find.ts
3895
- import { randomUUID as randomUUID4 } from "crypto";
3896
- import { resolveOutputFormat as resolveOutputFormat3 } from "@cleocode/lafs";
3736
+ import { randomUUID as randomUUID3 } from "crypto";
3737
+ import { resolveOutputFormat as resolveOutputFormat2 } from "@cleocode/lafs";
3897
3738
  import pc9 from "picocolors";
3898
3739
  var SkillsFindValidationError = class extends Error {
3899
3740
  code;
@@ -3925,7 +3766,7 @@ function registerSkillsFind(parent) {
3925
3766
  const mvi = details ? "full" : "standard";
3926
3767
  let format;
3927
3768
  try {
3928
- format = resolveOutputFormat3({
3769
+ format = resolveOutputFormat2({
3929
3770
  jsonFlag: opts.json ?? false,
3930
3771
  humanFlag: (opts.human ?? false) || isHuman(),
3931
3772
  projectDefault: "json"
@@ -4154,7 +3995,7 @@ function buildEnvelope3(operation, mvi, result, error) {
4154
3995
  schemaVersion: "1.0.0",
4155
3996
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4156
3997
  operation,
4157
- requestId: randomUUID4(),
3998
+ requestId: randomUUID3(),
4158
3999
  transport: "cli",
4159
4000
  strict: true,
4160
4001
  mvi,
@@ -4181,7 +4022,7 @@ function emitJsonError3(operation, mvi, code, message, category, details = {}) {
4181
4022
  // src/commands/skills/init.ts
4182
4023
  import { existsSync as existsSync10 } from "fs";
4183
4024
  import { mkdir, writeFile as writeFile3 } from "fs/promises";
4184
- import { join as join8 } from "path";
4025
+ import { join as join6 } from "path";
4185
4026
  import pc10 from "picocolors";
4186
4027
  function registerSkillsInit(parent) {
4187
4028
  parent.command("init").description("Create a new SKILL.md template").argument("[name]", "Skill name").option("-d, --dir <path>", "Output directory", ".").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format").action(
@@ -4207,7 +4048,7 @@ function registerSkillsInit(parent) {
4207
4048
  process.exit(1);
4208
4049
  }
4209
4050
  const skillName = name ?? "my-skill";
4210
- const skillDir = join8(opts.dir, skillName);
4051
+ const skillDir = join6(opts.dir, skillName);
4211
4052
  if (existsSync10(skillDir)) {
4212
4053
  const message = `Directory already exists: ${skillDir}`;
4213
4054
  if (format === "json") {
@@ -4250,7 +4091,7 @@ Provide detailed instructions for the AI agent here.
4250
4091
 
4251
4092
  Show example inputs and expected outputs.
4252
4093
  `;
4253
- await writeFile3(join8(skillDir, "SKILL.md"), template, "utf-8");
4094
+ await writeFile3(join6(skillDir, "SKILL.md"), template, "utf-8");
4254
4095
  const result = {
4255
4096
  name: skillName,
4256
4097
  directory: skillDir,
@@ -4264,95 +4105,88 @@ Show example inputs and expected outputs.
4264
4105
  console.log(pc10.green(`\u2713 Created skill template: ${skillDir}/SKILL.md`));
4265
4106
  console.log(pc10.dim("\nNext steps:"));
4266
4107
  console.log(pc10.dim(" 1. Edit SKILL.md with your instructions"));
4267
- console.log(pc10.dim(` 2. Validate: caamp skills validate ${join8(skillDir, "SKILL.md")}`));
4108
+ console.log(pc10.dim(` 2. Validate: caamp skills validate ${join6(skillDir, "SKILL.md")}`));
4268
4109
  console.log(pc10.dim(` 3. Install: caamp skills install ${skillDir}`));
4269
4110
  }
4270
4111
  );
4271
4112
  }
4272
4113
 
4273
4114
  // src/commands/skills/install.ts
4274
- import { existsSync as existsSync11 } from "fs";
4275
- import { createRequire } from "module";
4276
- import { pushWarning } from "@cleocode/lafs";
4277
4115
  import pc11 from "picocolors";
4278
- var cachedCore;
4279
- var defaultCoreResolver = async () => {
4280
- const require2 = createRequire(import.meta.url);
4281
- require2.resolve("@cleocode/core");
4282
- return await import("@cleocode/core");
4283
- };
4284
- var coreResolver = defaultCoreResolver;
4285
- async function resolveCore() {
4286
- if (cachedCore !== void 0) return cachedCore;
4287
- try {
4288
- cachedCore = await coreResolver();
4289
- } catch (err) {
4290
- cachedCore = null;
4291
- pushWarning({
4292
- code: "W_CORE_UNAVAILABLE",
4293
- severity: "warn",
4294
- message: "caamp running without @cleocode/core \u2014 trust gate skipped (skill installs not security-checked)",
4295
- context: {
4296
- error: err instanceof Error ? err.message : String(err)
4297
- }
4298
- });
4116
+ function renderProgress(event) {
4117
+ switch (event.kind) {
4118
+ case "marketplace-search":
4119
+ console.log(pc11.dim(`Searching marketplace for ${event.source}...`));
4120
+ return;
4121
+ case "marketplace-found":
4122
+ console.log(` Found: ${pc11.bold(event.name)} by ${event.author} (${pc11.dim(event.repo)})`);
4123
+ return;
4124
+ case "catalog-found":
4125
+ console.log(
4126
+ ` Found in catalog: ${pc11.bold(event.name)} v${event.version} (${pc11.dim(event.category)})`
4127
+ );
4128
+ return;
4299
4129
  }
4300
- return cachedCore;
4301
4130
  }
4302
- async function evaluateSkillTrustGate(localPath, source, force = false) {
4303
- const core = await resolveCore();
4304
- if (!core) {
4305
- return {
4306
- decision: "allow",
4307
- reason: "Trust gate skipped \u2014 @cleocode/core not available",
4308
- scan: {
4309
- skillName: localPath.split("/").filter(Boolean).pop() ?? localPath,
4310
- source,
4311
- trustLevel: "community",
4312
- verdict: "safe",
4313
- findings: [],
4314
- scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
4315
- summary: "core-unavailable"
4316
- }
4317
- };
4131
+ function categoryFor(err) {
4132
+ switch (err.code) {
4133
+ case ErrorCodes.NETWORK_ERROR:
4134
+ return ErrorCategories.TRANSIENT;
4135
+ case ErrorCodes.SKILL_NOT_FOUND:
4136
+ return ErrorCategories.NOT_FOUND;
4137
+ case ErrorCodes.SKILL_GATE_UNAVAILABLE:
4138
+ return ErrorCategories.INTERNAL;
4139
+ default:
4140
+ return ErrorCategories.VALIDATION;
4318
4141
  }
4319
- const scan = core.scanSkill(localPath, source);
4320
- const gate = core.shouldAllowInstall(scan, force);
4321
- return { decision: gate.decision, reason: gate.reason, scan };
4322
4142
  }
4323
- async function evaluateFederationGate(source, opts = {}) {
4324
- const core = await resolveCore();
4325
- if (!core) {
4326
- return {
4327
- decision: "allow",
4328
- reason: "Federation gate skipped \u2014 @cleocode/core not available",
4329
- peer: null,
4330
- isFederationSource: false,
4331
- computedChecksum: null,
4332
- expectedChecksum: null
4333
- };
4143
+ function reportRefusal(err, format, operation, mvi) {
4144
+ const scan = err.details.scan;
4145
+ if (format === "json") {
4146
+ if (scan && err.code === ErrorCodes.SKILL_TRUST_GATE_BLOCKED) {
4147
+ const envelope = buildEnvelope(
4148
+ operation,
4149
+ mvi,
4150
+ { scan },
4151
+ {
4152
+ code: err.code,
4153
+ message: err.message,
4154
+ category: categoryFor(err),
4155
+ retryable: false,
4156
+ retryAfterMs: null,
4157
+ details: {
4158
+ verdict: scan.verdict,
4159
+ trustLevel: scan.trustLevel,
4160
+ findingsCount: scan.findings.length
4161
+ }
4162
+ }
4163
+ );
4164
+ console.error(JSON.stringify(envelope, null, 2));
4165
+ } else {
4166
+ const details = {};
4167
+ if (err.details.availableSkills) details.availableSkills = err.details.availableSkills;
4168
+ if (err.details.federation) {
4169
+ details.expectedChecksum = err.details.federation.expectedChecksum;
4170
+ details.computedChecksum = err.details.federation.computedChecksum;
4171
+ details.peer = err.details.federation.peer?.url ?? null;
4172
+ }
4173
+ if (err.details.cause) details.cause = err.details.cause;
4174
+ emitJsonError(operation, mvi, err.code, err.message, categoryFor(err), details);
4175
+ }
4334
4176
  }
4335
- return core.evaluateFederationInstallGate({
4336
- source,
4337
- artefactPath: opts.artefactPath,
4338
- expectedChecksum: opts.expectedChecksum,
4339
- approveNewSource: opts.approveNewSource
4340
- });
4341
- }
4342
- async function recordSkillTrustBypass(scan, reason = null) {
4343
- const core = await resolveCore();
4344
- if (!core) return;
4345
- try {
4346
- core.recordTrustBypass(scan, reason);
4347
- } catch (err) {
4348
- const msg = err instanceof Error ? err.message : String(err);
4349
- pushWarning({
4350
- code: "W_AUDIT_LOG_FAILED",
4351
- severity: "warn",
4352
- message: "trust-bypass audit record failed",
4353
- context: { error: msg }
4354
- });
4177
+ console.error(pc11.red(err.message));
4178
+ if (scan && err.code === ErrorCodes.SKILL_TRUST_GATE_BLOCKED && format === "human") {
4179
+ for (const f of scan.findings.slice(0, 5)) {
4180
+ console.error(
4181
+ pc11.dim(` [${f.severity}] ${f.category} ${f.file}:${f.line} \u2014 ${f.description}`)
4182
+ );
4183
+ }
4184
+ console.error(pc11.yellow(" Use --force to override (audited)."));
4355
4185
  }
4186
+ if (err.details.availableSkills && format === "human") {
4187
+ console.log(pc11.dim(`Available skills: ${err.details.availableSkills.join(", ")}`));
4188
+ }
4189
+ process.exit(1);
4356
4190
  }
4357
4191
  function registerSkillsInstall(parent) {
4358
4192
  parent.command("install").description("Install a skill from GitHub, URL, marketplace, or registered skill library").argument("[source]", "Skill source (GitHub URL, owner/repo, @author/name, skill-name)").option(
@@ -4402,7 +4236,7 @@ function registerSkillsInstall(parent) {
4402
4236
  if (providers.length === 0) {
4403
4237
  const message = "No target providers found. Use --agent or --all.";
4404
4238
  if (format === "json") {
4405
- emitError2(
4239
+ emitError(
4406
4240
  operation,
4407
4241
  mvi,
4408
4242
  ErrorCodes.PROVIDER_NOT_FOUND,
@@ -4427,7 +4261,7 @@ function registerSkillsInstall(parent) {
4427
4261
  if (!source) {
4428
4262
  const message = "Missing required argument: source";
4429
4263
  if (format === "json") {
4430
- emitError2(
4264
+ emitError(
4431
4265
  operation,
4432
4266
  mvi,
4433
4267
  ErrorCodes.INVALID_INPUT,
@@ -4444,335 +4278,74 @@ function registerSkillsInstall(parent) {
4444
4278
  if (format === "human") {
4445
4279
  console.log(pc11.dim(`Installing to ${providers.length} provider(s)...`));
4446
4280
  }
4447
- let localPath;
4448
- let cleanup;
4449
- let skillName;
4450
- let sourceValue;
4451
- let sourceType;
4452
- if (isMarketplaceScoped(source)) {
4453
- const sourceResult = await handleMarketplaceSource(
4454
- source,
4281
+ let result;
4282
+ try {
4283
+ result = await installSkillFromSource(source, {
4455
4284
  providers,
4456
- opts.global ?? false,
4457
- format,
4458
- operation,
4459
- mvi
4460
- );
4461
- if (sourceResult.success) {
4462
- localPath = sourceResult.localPath;
4463
- cleanup = sourceResult.cleanup;
4464
- skillName = sourceResult.skillName;
4465
- sourceValue = sourceResult.sourceValue;
4466
- sourceType = sourceResult.sourceType;
4467
- } else {
4468
- process.exit(1);
4469
- }
4470
- } else {
4471
- const parsed = parseSource(source);
4472
- skillName = parsed.inferredName;
4473
- sourceValue = parsed.value;
4474
- sourceType = parsed.type;
4475
- if (parsed.type === "github" && parsed.owner && parsed.repo) {
4476
- try {
4477
- const result = await cloneRepo(parsed.owner, parsed.repo, parsed.ref, parsed.path);
4478
- localPath = result.localPath;
4479
- cleanup = result.cleanup;
4480
- } catch (error) {
4481
- const message = `Failed to clone GitHub repository: ${formatNetworkError(error)}`;
4482
- if (format === "json") {
4483
- emitJsonError(
4484
- operation,
4485
- mvi,
4486
- ErrorCodes.NETWORK_ERROR,
4487
- message,
4488
- ErrorCategories.TRANSIENT
4489
- );
4490
- }
4491
- console.error(pc11.red(message));
4492
- process.exit(1);
4493
- }
4494
- } else if (parsed.type === "gitlab" && parsed.owner && parsed.repo) {
4495
- try {
4496
- const result = await cloneGitLabRepo(
4497
- parsed.owner,
4498
- parsed.repo,
4499
- parsed.ref,
4500
- parsed.path
4501
- );
4502
- localPath = result.localPath;
4503
- cleanup = result.cleanup;
4504
- } catch (error) {
4505
- const message = `Failed to clone GitLab repository: ${formatNetworkError(error)}`;
4506
- if (format === "json") {
4507
- emitJsonError(
4508
- operation,
4509
- mvi,
4510
- ErrorCodes.NETWORK_ERROR,
4511
- message,
4512
- ErrorCategories.TRANSIENT
4513
- );
4514
- }
4515
- console.error(pc11.red(message));
4516
- process.exit(1);
4517
- }
4518
- } else if (parsed.type === "local") {
4519
- localPath = parsed.value;
4520
- const discovered = await discoverSkill(localPath);
4521
- if (discovered) {
4522
- skillName = discovered.name;
4523
- }
4524
- } else if (parsed.type === "package") {
4525
- if (!isCatalogAvailable()) {
4526
- const message = "No skill library registered. Register one with registerSkillLibraryFromPath() or set CAAMP_SKILL_LIBRARY env var.";
4527
- if (format === "json") {
4528
- emitJsonError(
4529
- operation,
4530
- mvi,
4531
- ErrorCodes.INVALID_INPUT,
4532
- message,
4533
- ErrorCategories.VALIDATION
4534
- );
4535
- }
4536
- console.error(pc11.red(message));
4537
- process.exit(1);
4538
- }
4539
- const catalogSkill = getSkill(parsed.inferredName);
4540
- if (catalogSkill) {
4541
- localPath = getSkillDir(catalogSkill.name);
4542
- skillName = catalogSkill.name;
4543
- sourceValue = `library:${catalogSkill.name}`;
4544
- sourceType = "library";
4545
- if (format === "human") {
4546
- console.log(
4547
- ` Found in catalog: ${pc11.bold(catalogSkill.name)} v${catalogSkill.version} (${pc11.dim(catalogSkill.category)})`
4548
- );
4549
- }
4550
- } else {
4551
- const message = `Skill not found in catalog: ${parsed.inferredName}`;
4552
- if (format === "json") {
4553
- emitJsonError(
4554
- operation,
4555
- mvi,
4556
- ErrorCodes.SKILL_NOT_FOUND,
4557
- message,
4558
- ErrorCategories.NOT_FOUND,
4559
- {
4560
- availableSkills: listSkills()
4561
- }
4562
- );
4563
- }
4564
- console.error(pc11.red(message));
4565
- console.log(pc11.dim("Available skills: " + listSkills().join(", ")));
4566
- process.exit(1);
4567
- }
4568
- } else {
4569
- const message = `Unsupported source type: ${parsed.type}`;
4570
- if (format === "json") {
4571
- emitJsonError(
4572
- operation,
4573
- mvi,
4574
- ErrorCodes.INVALID_FORMAT,
4575
- message,
4576
- ErrorCategories.VALIDATION
4577
- );
4578
- }
4579
- console.error(pc11.red(message));
4580
- process.exit(1);
4285
+ isGlobal: opts.global ?? false,
4286
+ force: opts.force ?? false,
4287
+ allowNewSource: opts.allowNewSource === true,
4288
+ recordLock: true,
4289
+ onProgress: format === "human" ? renderProgress : void 0
4290
+ });
4291
+ } catch (error) {
4292
+ if (error instanceof SkillInstallError) {
4293
+ reportRefusal(error, format, operation, mvi);
4581
4294
  }
4295
+ throw error;
4582
4296
  }
4583
- try {
4584
- if (!localPath) {
4585
- const message = "No local skill path resolved for installation";
4586
- if (format === "json") {
4587
- emitJsonError(
4588
- operation,
4589
- mvi,
4590
- ErrorCodes.INTERNAL_ERROR,
4591
- message,
4592
- ErrorCategories.INTERNAL
4593
- );
4594
- }
4595
- console.error(pc11.red(message));
4596
- process.exit(1);
4597
- }
4598
- if (sourceType !== "library" && sourceType !== "package") {
4599
- const fedGate = await evaluateFederationGate(sourceValue, {
4600
- artefactPath: localPath,
4601
- approveNewSource: opts.allowNewSource === true
4602
- });
4603
- if (fedGate.decision === "block-checksum") {
4604
- const message = `Federation install blocked: ${fedGate.reason}`;
4605
- if (format === "json") {
4606
- emitJsonError(
4607
- operation,
4608
- mvi,
4609
- ErrorCodes.FEDERATION_CHECKSUM_MISMATCH,
4610
- message,
4611
- ErrorCategories.VALIDATION,
4612
- {
4613
- expectedChecksum: fedGate.expectedChecksum,
4614
- computedChecksum: fedGate.computedChecksum
4615
- }
4616
- );
4617
- }
4618
- console.error(pc11.red(message));
4619
- process.exit(1);
4620
- }
4621
- if (fedGate.decision === "prompt-first-install") {
4622
- const message = `${fedGate.reason} Use --allow-new-source to approve in non-interactive contexts.`;
4623
- if (format === "json") {
4624
- emitJsonError(
4625
- operation,
4626
- mvi,
4627
- ErrorCodes.FEDERATION_UNKNOWN_SOURCE_INTERACTIVE_REQUIRED,
4628
- message,
4629
- ErrorCategories.VALIDATION,
4630
- { peer: fedGate.peer?.url ?? null }
4631
- );
4632
- }
4633
- console.error(pc11.yellow(message));
4634
- process.exit(1);
4635
- }
4636
- }
4637
- if (sourceType !== "library" && sourceType !== "package") {
4638
- const gate = await evaluateSkillTrustGate(localPath, sourceValue, opts.force ?? false);
4639
- if (gate.decision === "block") {
4640
- const message = `Trust gate blocked install: ${gate.reason}`;
4641
- if (format === "json") {
4642
- const envelope = buildEnvelope(
4643
- operation,
4644
- mvi,
4645
- { scan: gate.scan },
4646
- {
4647
- code: ErrorCodes.SKILL_TRUST_GATE_BLOCKED,
4648
- message,
4649
- category: ErrorCategories.VALIDATION,
4650
- retryable: false,
4651
- retryAfterMs: null,
4652
- details: {
4653
- verdict: gate.scan.verdict,
4654
- trustLevel: gate.scan.trustLevel,
4655
- findingsCount: gate.scan.findings.length
4656
- }
4657
- }
4658
- );
4659
- console.error(JSON.stringify(envelope, null, 2));
4660
- } else {
4661
- console.error(pc11.red(`
4662
- ${message}`));
4663
- for (const f of gate.scan.findings.slice(0, 5)) {
4664
- console.error(
4665
- pc11.dim(
4666
- ` [${f.severity}] ${f.category} ${f.file}:${f.line} \u2014 ${f.description}`
4667
- )
4668
- );
4669
- }
4670
- console.error(pc11.yellow(" Use --force to override (audited)."));
4671
- }
4672
- process.exit(1);
4673
- }
4674
- if (gate.decision === "ask") {
4675
- const message = `Trust gate requires confirmation: ${gate.reason}`;
4676
- if (format === "json") {
4677
- emitJsonError(
4678
- operation,
4679
- mvi,
4680
- ErrorCodes.SKILL_TRUST_GATE_BLOCKED,
4681
- message,
4682
- ErrorCategories.VALIDATION
4683
- );
4684
- }
4685
- console.error(pc11.red(message));
4686
- process.exit(1);
4687
- }
4688
- if (opts.force === true && gate.scan.verdict !== "safe") {
4689
- await recordSkillTrustBypass(gate.scan, "operator --force on caamp skills install");
4690
- }
4691
- }
4692
- const result = await dispatchInstallSkillAcrossProviders(
4693
- localPath,
4694
- skillName,
4695
- providers,
4696
- opts.global ?? false
4697
- );
4698
- if (result.success) {
4699
- const isGlobal = sourceType === "library" || sourceType === "package" ? true : opts.global ?? false;
4700
- await recordSkillInstall(
4701
- skillName,
4702
- sourceValue,
4703
- sourceValue,
4704
- sourceType,
4705
- result.linkedAgents,
4706
- result.canonicalPath,
4707
- isGlobal
4708
- );
4709
- const installedItem = {
4710
- name: skillName,
4711
- scopedName: sourceValue,
4712
- canonicalPath: result.canonicalPath,
4713
- providers: result.linkedAgents
4714
- };
4715
- const summary = {
4716
- installed: [installedItem],
4717
- failed: [],
4718
- count: {
4719
- installed: 1,
4720
- failed: 0,
4721
- total: 1
4722
- }
4723
- };
4724
- if (format === "json") {
4725
- outputSuccess(operation, mvi, summary);
4726
- } else {
4727
- console.log(pc11.green(`
4728
- \u2713 Installed ${pc11.bold(skillName)}`));
4729
- console.log(` Canonical: ${pc11.dim(result.canonicalPath)}`);
4730
- console.log(` Linked to: ${result.linkedAgents.join(", ")}`);
4731
- if (result.errors.length > 0) {
4732
- console.log(pc11.yellow("\nWarnings:"));
4733
- for (const err of result.errors) {
4734
- console.log(` ${pc11.yellow("!")} ${err}`);
4735
- }
4736
- }
4737
- }
4297
+ if (result.success) {
4298
+ const installedItem = {
4299
+ name: result.name,
4300
+ scopedName: result.sourceValue,
4301
+ canonicalPath: result.canonicalPath,
4302
+ providers: result.linkedAgents
4303
+ };
4304
+ const summary2 = {
4305
+ installed: [installedItem],
4306
+ failed: [],
4307
+ count: { installed: 1, failed: 0, total: 1 }
4308
+ };
4309
+ if (format === "json") {
4310
+ outputSuccess(operation, mvi, summary2);
4738
4311
  } else {
4739
- const summary = {
4740
- installed: [],
4741
- failed: [
4742
- {
4743
- name: skillName,
4744
- error: result.errors.join(", ")
4745
- }
4746
- ],
4747
- count: {
4748
- installed: 0,
4749
- failed: 1,
4750
- total: 1
4751
- }
4752
- };
4753
- if (format === "json") {
4754
- const envelope = buildEnvelope(operation, mvi, summary, {
4755
- code: ErrorCodes.INSTALL_FAILED,
4756
- message: result.errors.join(", "),
4757
- category: ErrorCategories.INTERNAL,
4758
- retryable: false,
4759
- retryAfterMs: null,
4760
- details: { skillName, sourceValue }
4761
- });
4762
- console.error(JSON.stringify(envelope, null, 2));
4763
- } else {
4764
- console.log(pc11.yellow(`
4765
- \u2717 Failed to install ${pc11.bold(skillName)}`));
4766
- console.log(pc11.yellow("Errors:"));
4312
+ console.log(pc11.green(`
4313
+ \u2713 Installed ${pc11.bold(result.name)}`));
4314
+ console.log(` Canonical: ${pc11.dim(result.canonicalPath)}`);
4315
+ console.log(` Linked to: ${result.linkedAgents.join(", ")}`);
4316
+ if (result.errors.length > 0) {
4317
+ console.log(pc11.yellow("\nWarnings:"));
4767
4318
  for (const err of result.errors) {
4768
4319
  console.log(` ${pc11.yellow("!")} ${err}`);
4769
4320
  }
4770
4321
  }
4771
- process.exit(1);
4772
4322
  }
4773
- } finally {
4774
- if (cleanup) await cleanup();
4323
+ return;
4775
4324
  }
4325
+ const summary = {
4326
+ installed: [],
4327
+ failed: [{ name: result.name, error: result.errors.join(", ") }],
4328
+ count: { installed: 0, failed: 1, total: 1 }
4329
+ };
4330
+ if (format === "json") {
4331
+ const envelope = buildEnvelope(operation, mvi, summary, {
4332
+ code: ErrorCodes.INSTALL_FAILED,
4333
+ message: result.errors.join(", "),
4334
+ category: ErrorCategories.INTERNAL,
4335
+ retryable: false,
4336
+ retryAfterMs: null,
4337
+ details: { skillName: result.name, sourceValue: result.sourceValue }
4338
+ });
4339
+ console.error(JSON.stringify(envelope, null, 2));
4340
+ } else {
4341
+ console.log(pc11.yellow(`
4342
+ \u2717 Failed to install ${pc11.bold(result.name)}`));
4343
+ console.log(pc11.yellow("Errors:"));
4344
+ for (const err of result.errors) {
4345
+ console.log(` ${pc11.yellow("!")} ${err}`);
4346
+ }
4347
+ }
4348
+ process.exit(1);
4776
4349
  }
4777
4350
  );
4778
4351
  }
@@ -4780,7 +4353,7 @@ async function handleProfileInstall(profileName, providers, isGlobal, format, op
4780
4353
  if (!isCatalogAvailable()) {
4781
4354
  const message = "No skill library registered. Register one with registerSkillLibraryFromPath() or set CAAMP_SKILL_LIBRARY env var.";
4782
4355
  if (format === "json") {
4783
- emitError2(operation, mvi, ErrorCodes.INVALID_INPUT, message, ErrorCategories.VALIDATION);
4356
+ emitError(operation, mvi, ErrorCodes.INVALID_INPUT, message, ErrorCategories.VALIDATION);
4784
4357
  }
4785
4358
  console.error(pc11.red(message));
4786
4359
  process.exit(1);
@@ -4814,22 +4387,20 @@ async function handleProfileInstall(profileName, providers, isGlobal, format, op
4814
4387
  const installed = [];
4815
4388
  const failed = [];
4816
4389
  for (const name of profileSkills) {
4817
- const skillDir = getSkillDir(name);
4818
4390
  try {
4819
- const result = await dispatchInstallSkillAcrossProviders(skillDir, name, providers, isGlobal);
4391
+ const result = await installResolvedSkill(
4392
+ {
4393
+ localPath: getSkillDir(name),
4394
+ skillName: name,
4395
+ sourceValue: `library:${name}`,
4396
+ sourceType: "library"
4397
+ },
4398
+ { providers, isGlobal, recordLock: true }
4399
+ );
4820
4400
  if (result.success) {
4821
4401
  if (format === "human") {
4822
4402
  console.log(pc11.green(` + ${name}`));
4823
4403
  }
4824
- await recordSkillInstall(
4825
- name,
4826
- `library:${name}`,
4827
- `library:${name}`,
4828
- "library",
4829
- result.linkedAgents,
4830
- result.canonicalPath,
4831
- true
4832
- );
4833
4404
  installed.push({
4834
4405
  name,
4835
4406
  scopedName: `library:${name}`,
@@ -4890,89 +4461,10 @@ ${pc11.green(`${installed.length} installed`)}, ${failed.length > 0 ? pc11.yello
4890
4461
  }
4891
4462
  }
4892
4463
  }
4893
- async function handleMarketplaceSource(source, _providers, _isGlobal, format, operation, mvi) {
4894
- if (format === "human") {
4895
- console.log(pc11.dim(`Searching marketplace for ${source}...`));
4896
- }
4897
- const client = new MarketplaceClient();
4898
- let skill;
4899
- try {
4900
- skill = await client.getSkill(source);
4901
- } catch (error) {
4902
- const message = `Marketplace lookup failed: ${formatNetworkError(error)}`;
4903
- if (format === "json") {
4904
- emitJsonError(operation, mvi, ErrorCodes.NETWORK_ERROR, message, ErrorCategories.TRANSIENT);
4905
- }
4906
- console.error(pc11.red(message));
4907
- return { success: false };
4908
- }
4909
- if (!skill) {
4910
- const message = `Skill not found: ${source}`;
4911
- if (format === "json") {
4912
- emitJsonError(operation, mvi, ErrorCodes.SKILL_NOT_FOUND, message, ErrorCategories.NOT_FOUND);
4913
- }
4914
- console.error(pc11.red(message));
4915
- return { success: false };
4916
- }
4917
- if (format === "human") {
4918
- console.log(
4919
- ` Found: ${pc11.bold(skill.name)} by ${skill.author} (${pc11.dim(skill.repoFullName)})`
4920
- );
4921
- }
4922
- const parsed = parseSource(skill.githubUrl);
4923
- if (parsed.type !== "github" || !parsed.owner || !parsed.repo) {
4924
- const message = "Could not resolve GitHub source";
4925
- if (format === "json") {
4926
- emitJsonError(operation, mvi, ErrorCodes.INVALID_FORMAT, message, ErrorCategories.VALIDATION);
4927
- }
4928
- console.error(pc11.red(message));
4929
- return { success: false };
4930
- }
4931
- try {
4932
- const subPathCandidates = buildSkillSubPathCandidates(skill.path, parsed.path);
4933
- let cloneError;
4934
- let cloned = false;
4935
- let localPath;
4936
- let cleanup;
4937
- for (const subPath of subPathCandidates) {
4938
- try {
4939
- const result = await cloneRepo(parsed.owner, parsed.repo, parsed.ref, subPath);
4940
- if (subPath && !existsSync11(result.localPath)) {
4941
- await result.cleanup();
4942
- continue;
4943
- }
4944
- localPath = result.localPath;
4945
- cleanup = result.cleanup;
4946
- cloned = true;
4947
- break;
4948
- } catch (error) {
4949
- cloneError = error;
4950
- }
4951
- }
4952
- if (!cloned) {
4953
- throw cloneError ?? new Error("Unable to resolve skill path from marketplace metadata");
4954
- }
4955
- return {
4956
- success: true,
4957
- localPath,
4958
- cleanup,
4959
- skillName: skill.name,
4960
- sourceValue: skill.githubUrl,
4961
- sourceType: parsed.type
4962
- };
4963
- } catch (error) {
4964
- const message = `Failed to fetch source repository: ${formatNetworkError(error)}`;
4965
- if (format === "json") {
4966
- emitJsonError(operation, mvi, ErrorCodes.NETWORK_ERROR, message, ErrorCategories.TRANSIENT);
4967
- }
4968
- console.error(pc11.red(message));
4969
- return { success: false };
4970
- }
4971
- }
4972
4464
 
4973
4465
  // src/commands/skills/list.ts
4974
- import { randomUUID as randomUUID5 } from "crypto";
4975
- import { resolveOutputFormat as resolveOutputFormat4 } from "@cleocode/lafs";
4466
+ import { randomUUID as randomUUID4 } from "crypto";
4467
+ import { resolveOutputFormat as resolveOutputFormat3 } from "@cleocode/lafs";
4976
4468
  import pc12 from "picocolors";
4977
4469
  function registerSkillsList(parent) {
4978
4470
  parent.command("list").description("List installed skills").option("-g, --global", "List global skills").option("-a, --agent <name>", "List skills for specific agent").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format").action(async (opts) => {
@@ -4980,7 +4472,7 @@ function registerSkillsList(parent) {
4980
4472
  const mvi = "standard";
4981
4473
  let format;
4982
4474
  try {
4983
- format = resolveOutputFormat4({
4475
+ format = resolveOutputFormat3({
4984
4476
  jsonFlag: opts.json ?? false,
4985
4477
  humanFlag: (opts.human ?? false) || isHuman(),
4986
4478
  projectDefault: "json"
@@ -5053,7 +4545,7 @@ function buildEnvelope4(operation, mvi, result, error) {
5053
4545
  schemaVersion: "1.0.0",
5054
4546
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5055
4547
  operation,
5056
- requestId: randomUUID5(),
4548
+ requestId: randomUUID4(),
5057
4549
  transport: "cli",
5058
4550
  strict: true,
5059
4551
  mvi,
@@ -5310,12 +4802,14 @@ ${outdated.length} skill(s) have updates available:
5310
4802
  skipped.push(skill.name);
5311
4803
  continue;
5312
4804
  }
5313
- const installResult = await dispatchInstallSkillAcrossProviders(
5314
- localPath,
5315
- skill.name,
5316
- providers,
5317
- entry.isGlobal,
5318
- entry.projectDir
4805
+ const installResult = await installResolvedSkill(
4806
+ {
4807
+ localPath,
4808
+ skillName: skill.name,
4809
+ sourceValue: entry.source,
4810
+ sourceType: parsed.type
4811
+ },
4812
+ { providers, isGlobal: entry.isGlobal, projectDir: entry.projectDir }
5319
4813
  );
5320
4814
  if (installResult.success) {
5321
4815
  await recordSkillInstall(
@@ -5379,7 +4873,7 @@ ${outdated.length} skill(s) have updates available:
5379
4873
  }
5380
4874
 
5381
4875
  // src/commands/skills/validate.ts
5382
- import { resolveOutputFormat as resolveOutputFormat5 } from "@cleocode/lafs";
4876
+ import { resolveOutputFormat as resolveOutputFormat4 } from "@cleocode/lafs";
5383
4877
  import pc15 from "picocolors";
5384
4878
  function registerSkillsValidate(parent) {
5385
4879
  parent.command("validate").description("Validate SKILL.md format").argument("[path]", "Path to SKILL.md", "SKILL.md").option("--json", "Output as JSON (default)").option("--human", "Output in human-readable format").action(async (path, opts) => {
@@ -5387,7 +4881,7 @@ function registerSkillsValidate(parent) {
5387
4881
  const mvi = "standard";
5388
4882
  let format;
5389
4883
  try {
5390
- format = resolveOutputFormat5({
4884
+ format = resolveOutputFormat4({
5391
4885
  jsonFlag: opts.json ?? false,
5392
4886
  humanFlag: (opts.human ?? false) || isHuman(),
5393
4887
  projectDefault: "json"