@theholocron/cli 2.0.2 → 2.1.1

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.
@@ -1,5 +1,4 @@
1
1
  import { ProviderApiError } from "@theholocron/http-client";
2
-
3
2
  //#region src/capabilities/index.d.ts
4
3
  /**
5
4
  * Capability interfaces — the contracts that providers implement.
@@ -276,7 +275,8 @@ interface Issues extends ProviderIdentity {
276
275
  create(input: {
277
276
  summary: string;
278
277
  body?: string;
279
- labels?: string[]; /** Numeric id or exact title (case-insensitive). */
278
+ labels?: string[];
279
+ /** Numeric id or exact title (case-insensitive). */
280
280
  milestone?: string;
281
281
  }): Promise<{
282
282
  key: string;
@@ -322,7 +322,8 @@ interface Deployment extends ProviderIdentity {
322
322
  /** Create if missing, otherwise return existing. Idempotent. */
323
323
  ensureProject(input: {
324
324
  name: string;
325
- framework?: string; /** "owner/repo" — passed when linking to a Git provider. */
325
+ framework?: string;
326
+ /** "owner/repo" — passed when linking to a Git provider. */
326
327
  repo?: string;
327
328
  rootDirectory?: string;
328
329
  }): Promise<DeploymentProject>;
package/dist/cli.mjs CHANGED
@@ -3590,7 +3590,8 @@ async function runSetup(input) {
3590
3590
  "prd"
3591
3591
  ]) {
3592
3592
  steps.push(await runStep("vault", `ensureEnvironment ${envName}`, dryRun, async () => {
3593
- return `${envName} ${(await vault.ensureEnvironment(config.name, envName)).alreadyExists ? "exists" : "created"}`;
3593
+ const result = await vault.ensureEnvironment(config.name, envName);
3594
+ return `${envName} ${result.alreadyExists ? "exists" : "created"}`;
3594
3595
  }));
3595
3596
  print(formatStep(steps[steps.length - 1]));
3596
3597
  }
@@ -3676,6 +3677,21 @@ async function runSetup(input) {
3676
3677
  summary
3677
3678
  };
3678
3679
  }
3680
+ /**
3681
+ * Fetch a single SKILL.md from its upstream GitHub source.
3682
+ * Verifies the SHA-256 hash when `computedHash` is present in the lock entry.
3683
+ */
3684
+ async function fetchExternalSkill(entry) {
3685
+ if (entry.sourceType !== "github") throw new Error(`unsupported sourceType: ${entry.sourceType}`);
3686
+ const url = `https://raw.githubusercontent.com/${entry.source}/HEAD/${entry.skillPath}`;
3687
+ const res = await fetch(url);
3688
+ if (!res.ok) throw new Error(`HTTP ${res.status} fetching ${url}`);
3689
+ const content = await res.text();
3690
+ return {
3691
+ content,
3692
+ stale: !!entry.computedHash && createHash("sha256").update(content).digest("hex") !== entry.computedHash
3693
+ };
3694
+ }
3679
3695
  const AGENTS_SKILLS_ROOT = ".agents/skills";
3680
3696
  /** Relative path of the agent-specific symlink. undefined = unsupported agent. */
3681
3697
  const AGENT_SYMLINK_PATHS = { claude: (name) => `.claude/skills/${name}` };
@@ -3723,9 +3739,45 @@ async function installSkills({ agent, skills, repoRoot }) {
3723
3739
  await symlink(relative(dirname(symlinkPath), agentsDir).replace(/\\/g, "/"), symlinkPath);
3724
3740
  installed.push(name);
3725
3741
  }
3726
- if (installed.length > 0 || stale.length > 0 || missing.length > 0) await updateSkillsGitignore(gitignorePath, existingContent, [...installed, ...missing], symlinkFn);
3742
+ const externalFailed = [];
3743
+ const externalStale = [];
3744
+ if (missing.length > 0) {
3745
+ let lock = null;
3746
+ try {
3747
+ lock = JSON.parse(await readFile(join(skillsRoot, "skills-lock.json"), "utf8"));
3748
+ } catch {}
3749
+ if (lock?.skills) for (const name of [...missing]) {
3750
+ const entry = lock.skills[name];
3751
+ if (!entry) continue;
3752
+ try {
3753
+ const { content, stale: isStale } = await fetchExternalSkill(entry);
3754
+ const agentsDir = join(repoRoot, AGENTS_SKILLS_ROOT, name);
3755
+ await mkdir(agentsDir, { recursive: true });
3756
+ await writeFile(join(agentsDir, "SKILL.md"), content);
3757
+ const symlinkPath = join(repoRoot, symlinkFn(name));
3758
+ await mkdir(dirname(symlinkPath), { recursive: true });
3759
+ try {
3760
+ await unlink(symlinkPath);
3761
+ } catch {}
3762
+ await symlink(relative(dirname(symlinkPath), agentsDir).replace(/\\/g, "/"), symlinkPath);
3763
+ missing.splice(missing.indexOf(name), 1);
3764
+ installed.push(name);
3765
+ if (isStale) externalStale.push(name);
3766
+ } catch {
3767
+ missing.splice(missing.indexOf(name), 1);
3768
+ externalFailed.push(name);
3769
+ }
3770
+ }
3771
+ }
3772
+ if (installed.length > 0 || stale.length > 0 || missing.length > 0 || externalFailed.length > 0) await updateSkillsGitignore(gitignorePath, existingContent, [
3773
+ ...installed,
3774
+ ...missing,
3775
+ ...externalFailed
3776
+ ], symlinkFn);
3727
3777
  const parts = [`installed ${installed.length}`];
3728
3778
  if (stale.length > 0) parts.push(`pruned: ${stale.join(", ")}`);
3779
+ if (externalStale.length > 0) parts.push(`stale: ${externalStale.join(", ")} (run \`holocron skills update\` to refresh)`);
3780
+ if (externalFailed.length > 0) parts.push(`fetch failed: ${externalFailed.join(", ")}`);
3729
3781
  if (missing.length > 0) parts.push(`unknown: ${missing.join(", ")}`);
3730
3782
  return parts.join("; ");
3731
3783
  }
@@ -3750,9 +3802,10 @@ async function copyDirRecursive(src, dest) {
3750
3802
  }
3751
3803
  }
3752
3804
  async function updateSkillsGitignore(gitignorePath, existingContent, skills, symlinkFn) {
3805
+ const entries = [`/${AGENTS_SKILLS_ROOT}/`, ...skills.map((n) => `/${symlinkFn(n)}`)];
3753
3806
  const block = [
3754
3807
  GITIGNORE_BLOCK_START,
3755
- ...[`/${AGENTS_SKILLS_ROOT}/`, ...skills.map((n) => `/${symlinkFn(n)}`)],
3808
+ ...entries,
3756
3809
  GITIGNORE_BLOCK_END
3757
3810
  ].join("\n");
3758
3811
  let content;
package/dist/index.d.mts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { Analytics, Auth, AuthDescription, AuthEvent, AuthEventType, AuthIdentity, AuthUser, CARDINALITY, CapabilityImpls, CapabilityKey, Cardinality, CardinalityFor, Ci, CiRun, CiRunFilter, CiRunStatus, ConnectionStringOptions, CreateAuthUserInput, Deployment, DeploymentProject, DeploymentProjectSettings, DeploymentRecord, DeploymentTarget, DeploymentTrigger, Dns, DnsRecord, DnsRecordType, EnsureResult, Environment, EnvironmentReviewer, Environments, Issue, IssueSearchFilter, Issues, LabelDef, LifecycleResult, LifecycleSlot, NormalizedAuthUser, Notifications, Observability, ParseWebhookInput, ProviderApiError, ProviderIdentity, REQUIRED_CAPABILITIES, RepoRef, RepoSettings, ResolvedCapability, Ruleset, SecretScope, Secrets, Source, StatusCategory, Storage, StorageBranch, TeamEntry, TeamPermission, Tooling, ToolingDoctorReport, TrackerDoctorReport, TrackerUser, Vault, WebhookDashboardInfo, WebhookVerificationError, isMulti } from "./capabilities/index.mjs";
2
2
  import { AuthError, RequestOptions, ResolveTokenConfig as ResolveTokenConfig$1, ResolveTokenInput, RestClient, RestClientConfig, createRestClient } from "@theholocron/http-client";
3
-
4
3
  //#region src/auth-resolver.d.ts
5
4
  type ResolveTokenConfig = Omit<ResolveTokenConfig$1, "getKeyringToken">;
6
5
  /** Wraps `createResolveToken` from `@theholocron/http` and injects the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theholocron/cli",
3
- "version": "2.0.2",
3
+ "version": "2.1.1",
4
4
  "description": "The Holocron CLI — a pluggable, capability-based orchestrator for spinning up and operating software projects.",
5
5
  "homepage": "https://github.com/theholocron/holocron/tree/main/packages/cli#readme",
6
6
  "bugs": "https://github.com/theholocron/holocron/issues",