@morphist/aspects 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6311,12 +6311,12 @@ var icons = {
6311
6311
  var LINE_COLORS = [colors2.cyan, colors2.teal, colors2.orange, colors2.red];
6312
6312
  function morphistBanner() {
6313
6313
  const lines = [
6314
- " █████╗ ███████╗██████╗ ███████╗ ██████╗████████╗███████╗",
6315
- " ██╔══██╗ ██╔════╝██╔══██╗██╔════╝██╔════╝╚══██╔══╝██╔════╝",
6316
- " ███████║ ███████╗██████╔╝█████╗ ██║ ██║ ███████╗",
6317
- " ██╔══██║ ╚════██║██╔═══╝ ██╔══╝ ██║ ██║ ╚════██║",
6318
- " ██║ ██║ ███████║██║ ███████╗╚██████╗ ██║ ███████║",
6319
- " ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚══════╝ ╚═════╝ ╚═╝ ╚══════╝"
6314
+ " █████╗ ███████╗██████╗ ███████╗ ██████╗████████╗███████╗",
6315
+ " ██╔══██╗██╔════╝██╔══██╗██╔════╝██╔════╝╚══██╔══╝██╔════╝",
6316
+ " ███████║███████╗██████╔╝█████╗ ██║ ██║ ███████╗",
6317
+ " ██╔══██║╚════██║██╔═══╝ ██╔══╝ ██║ ██║ ╚════██║",
6318
+ " ██║ ██║███████║██║ ███████╗╚██████╗ ██║ ███████║",
6319
+ " ╚═╝ ╚═╝╚══════╝╚═╝ ╚══════╝ ╚═════╝ ╚═╝ ╚══════╝"
6320
6320
  ];
6321
6321
  console.log();
6322
6322
  console.log(colors2.cyan(lines[0]));
@@ -6327,9 +6327,87 @@ function morphistBanner() {
6327
6327
  console.log(colors2.red(lines[5]));
6328
6328
  console.log();
6329
6329
  }
6330
+ // package.json
6331
+ var package_default = {
6332
+ name: "@morphist/aspects",
6333
+ version: "0.1.3",
6334
+ description: "Package manager for AI personality aspects - like npm for agent personas",
6335
+ author: "Morphist <hello@morphist.ai>",
6336
+ license: "MIT",
6337
+ type: "module",
6338
+ repository: {
6339
+ type: "git",
6340
+ url: "git+https://github.com/aimorphist/aspects.git"
6341
+ },
6342
+ homepage: "https://github.com/aimorphist/aspects#readme",
6343
+ bugs: {
6344
+ url: "https://github.com/aimorphist/aspects/issues"
6345
+ },
6346
+ keywords: [
6347
+ "ai",
6348
+ "personality",
6349
+ "aspects",
6350
+ "agents",
6351
+ "llm",
6352
+ "prompts",
6353
+ "voice",
6354
+ "cli",
6355
+ "package-manager"
6356
+ ],
6357
+ bin: {
6358
+ aspects: "dist/cli.js"
6359
+ },
6360
+ files: [
6361
+ "dist"
6362
+ ],
6363
+ scripts: {
6364
+ dev: "bun run src/cli.ts",
6365
+ aspects: "bun run src/cli.ts",
6366
+ create: "bun run src/cli.ts create",
6367
+ new: "bun run src/cli.ts create",
6368
+ init: "bun run src/cli.ts create",
6369
+ add: "bun run src/cli.ts add",
6370
+ get: "bun run src/cli.ts add",
6371
+ list: "bun run src/cli.ts list",
6372
+ compile: "bun run src/cli.ts compile",
6373
+ edit: "bun run src/cli.ts edit",
6374
+ build: "bun build src/cli.ts scripts/postinstall.ts --outdir dist --target node --format esm",
6375
+ typecheck: "tsc --noEmit",
6376
+ prepublishOnly: "bun run build",
6377
+ postinstall: "node dist/postinstall.js || true",
6378
+ validate: "bun run scripts/validate-pr.ts",
6379
+ scan: "bun run scripts/security-scan.ts",
6380
+ test: "bun test tests/unit",
6381
+ "test:e2e": "./scripts/test-cli.sh",
6382
+ "test:unit": "bun test tests/unit",
6383
+ "test:integration": "ASPECTS_REGISTRY_URL=http://localhost:5173/api/v1 bun test tests/integration",
6384
+ "test:all": "bun test tests"
6385
+ },
6386
+ dependencies: {
6387
+ "@clack/prompts": "^0.11.0",
6388
+ "@noble/hashes": "^2.0.1",
6389
+ "@scure/base": "^2.0.0",
6390
+ citty: "^0.1.6",
6391
+ consola: "^3.2.3",
6392
+ ofetch: "^1.3.4",
6393
+ picocolors: "^1.1.1",
6394
+ zod: "^4.3.5"
6395
+ },
6396
+ devDependencies: {
6397
+ "@types/bun": "latest",
6398
+ "@types/node": "^22",
6399
+ typescript: "^5"
6400
+ },
6401
+ engines: {
6402
+ node: ">=18"
6403
+ },
6404
+ publishConfig: {
6405
+ access: "public"
6406
+ }
6407
+ };
6330
6408
 
6331
6409
  // src/commands/create.ts
6332
- import { writeFile as writeFile4, readFile as readFile5, stat as stat3, mkdir as mkdir4 } from "node:fs/promises";
6410
+ import { writeFile as writeFile4, readFile as readFile4, stat as stat3, mkdir as mkdir4 } from "node:fs/promises";
6333
6411
  import { join as join5 } from "node:path";
6334
6412
  import { execSync } from "node:child_process";
6335
6413
 
@@ -20607,11 +20685,18 @@ var OFFICIAL_CATEGORIES = [
20607
20685
  "guide"
20608
20686
  ];
20609
20687
  var FIELD_LIMITS = {
20688
+ nameMin: 2,
20610
20689
  name: 50,
20690
+ displayNameMin: 2,
20611
20691
  displayName: 100,
20692
+ taglineMin: 10,
20612
20693
  tagline: 200,
20694
+ categoryMin: 2,
20695
+ category: 20,
20696
+ tagMin: 2,
20613
20697
  tag: 30,
20614
20698
  maxTags: 10,
20699
+ promptMin: 10,
20615
20700
  prompt: 50000,
20616
20701
  author: 100,
20617
20702
  publisher: 50,
@@ -20631,19 +20716,17 @@ var FIELD_LIMITS = {
20631
20716
  maxInstructions: 25
20632
20717
  };
20633
20718
  var aspectSchema = exports_external.object({
20634
- schemaVersion: exports_external.number().default(1),
20635
- name: exports_external.string().min(1, "name is required").max(FIELD_LIMITS.name, `name must be ${FIELD_LIMITS.name} chars or less`),
20636
- publisher: exports_external.string().max(FIELD_LIMITS.publisher, `publisher must be ${FIELD_LIMITS.publisher} chars or less`).optional(),
20719
+ schemaVersion: exports_external.literal(1),
20720
+ name: exports_external.string().min(FIELD_LIMITS.nameMin, `name must be at least ${FIELD_LIMITS.nameMin} chars`).max(FIELD_LIMITS.name, `name must be ${FIELD_LIMITS.name} chars or less`),
20721
+ publisher: exports_external.string().min(1, "publisher is required").max(FIELD_LIMITS.publisher, `publisher must be ${FIELD_LIMITS.publisher} chars or less`).default("anon-user"),
20637
20722
  version: exports_external.string().default("0.0.0"),
20638
- displayName: exports_external.string().min(1, "displayName is required").max(FIELD_LIMITS.displayName, `displayName must be ${FIELD_LIMITS.displayName} chars or less`),
20639
- tagline: exports_external.string().min(1, "tagline is required").max(FIELD_LIMITS.tagline, `tagline must be ${FIELD_LIMITS.tagline} chars or less`),
20723
+ displayName: exports_external.string().min(FIELD_LIMITS.displayNameMin, `displayName must be at least ${FIELD_LIMITS.displayNameMin} chars`).max(FIELD_LIMITS.displayName, `displayName must be ${FIELD_LIMITS.displayName} chars or less`),
20724
+ tagline: exports_external.string().min(FIELD_LIMITS.taglineMin, `tagline must be at least ${FIELD_LIMITS.taglineMin} chars`).max(FIELD_LIMITS.tagline, `tagline must be ${FIELD_LIMITS.tagline} chars or less`),
20640
20725
  icon: exports_external.string().max(FIELD_LIMITS.icon, `icon must be ${FIELD_LIMITS.icon} chars or less`).optional(),
20641
20726
  author: exports_external.string().max(FIELD_LIMITS.author, `author must be ${FIELD_LIMITS.author} chars or less`).optional(),
20642
20727
  license: exports_external.string().max(FIELD_LIMITS.license, `license must be ${FIELD_LIMITS.license} chars or less`).optional(),
20643
- category: exports_external.enum(OFFICIAL_CATEGORIES, {
20644
- message: `category must be one of: ${OFFICIAL_CATEGORIES.join(", ")}`
20645
- }),
20646
- tags: exports_external.array(exports_external.string().max(FIELD_LIMITS.tag, `each tag must be ${FIELD_LIMITS.tag} chars or less`)).max(FIELD_LIMITS.maxTags, `maximum ${FIELD_LIMITS.maxTags} tags allowed`).optional(),
20728
+ category: exports_external.string().min(FIELD_LIMITS.categoryMin, `Category must be at least ${FIELD_LIMITS.categoryMin} characters`).max(FIELD_LIMITS.category, `Category must be at most ${FIELD_LIMITS.category} characters`).regex(/^[a-zA-Z0-9-]+$/, "Category must be alphanumeric with hyphens only"),
20729
+ tags: exports_external.array(exports_external.string().min(FIELD_LIMITS.tagMin, `each tag must be at least ${FIELD_LIMITS.tagMin} chars`).max(FIELD_LIMITS.tag, `each tag must be ${FIELD_LIMITS.tag} chars or less`)).max(FIELD_LIMITS.maxTags, `maximum ${FIELD_LIMITS.maxTags} tags allowed`).optional(),
20647
20730
  voiceHints: exports_external.object({
20648
20731
  speed: exports_external.enum(["slow", "normal", "fast"]).optional(),
20649
20732
  emotions: exports_external.array(exports_external.string().max(FIELD_LIMITS.emotion, `each emotion must be ${FIELD_LIMITS.emotion} chars or less`)).max(FIELD_LIMITS.maxEmotions, `maximum ${FIELD_LIMITS.maxEmotions} emotions allowed`).optional(),
@@ -20656,21 +20739,6 @@ var aspectSchema = exports_external.object({
20656
20739
  })).refine((modes) => Object.keys(modes).length <= FIELD_LIMITS.maxModes, {
20657
20740
  message: `maximum ${FIELD_LIMITS.maxModes} modes allowed`
20658
20741
  }).optional(),
20659
- resources: exports_external.object({
20660
- voice: exports_external.object({
20661
- recommended: exports_external.object({
20662
- provider: exports_external.string(),
20663
- voiceId: exports_external.string()
20664
- }).optional()
20665
- }).optional(),
20666
- model: exports_external.object({
20667
- recommended: exports_external.object({
20668
- provider: exports_external.string(),
20669
- modelId: exports_external.string()
20670
- }).optional()
20671
- }).optional(),
20672
- skills: exports_external.array(exports_external.string()).optional()
20673
- }).optional(),
20674
20742
  directives: exports_external.array(exports_external.object({
20675
20743
  id: exports_external.string().max(FIELD_LIMITS.directiveId, `directive id must be ${FIELD_LIMITS.directiveId} chars or less`),
20676
20744
  rule: exports_external.string().max(FIELD_LIMITS.directiveRule, `directive rule must be ${FIELD_LIMITS.directiveRule} chars or less`),
@@ -20680,11 +20748,11 @@ var aspectSchema = exports_external.object({
20680
20748
  id: exports_external.string().max(FIELD_LIMITS.instructionId, `instruction id must be ${FIELD_LIMITS.instructionId} chars or less`),
20681
20749
  rule: exports_external.string().max(FIELD_LIMITS.instructionRule, `instruction rule must be ${FIELD_LIMITS.instructionRule} chars or less`)
20682
20750
  })).max(FIELD_LIMITS.maxInstructions, `maximum ${FIELD_LIMITS.maxInstructions} instructions allowed`).optional(),
20683
- prompt: exports_external.string().min(1, "prompt is required").max(FIELD_LIMITS.prompt, `prompt must be ${FIELD_LIMITS.prompt} chars or less`)
20751
+ prompt: exports_external.string().min(FIELD_LIMITS.promptMin, `prompt must be at least ${FIELD_LIMITS.promptMin} chars`).max(FIELD_LIMITS.prompt, `prompt must be ${FIELD_LIMITS.prompt} chars or less`)
20684
20752
  });
20685
20753
 
20686
20754
  // src/commands/set.ts
20687
- import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3, readdir } from "node:fs/promises";
20755
+ import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3, readdir } from "node:fs/promises";
20688
20756
  import { join as join4 } from "node:path";
20689
20757
 
20690
20758
  // src/utils/colors.ts
@@ -20704,21 +20772,57 @@ var c3 = {
20704
20772
  value: (text) => import_picocolors3.default.white(text),
20705
20773
  highlight: (text) => import_picocolors3.default.bold(import_picocolors3.default.cyan(text)),
20706
20774
  muted: (text) => import_picocolors3.default.dim(text),
20707
- cmd: (text) => import_picocolors3.default.cyan(text)
20775
+ cmd: (text) => import_picocolors3.default.cyan(text),
20776
+ file: (text) => import_picocolors3.default.yellow(text)
20708
20777
  };
20709
20778
  var icons2 = {
20710
20779
  success: import_picocolors3.default.green("✓"),
20711
20780
  error: import_picocolors3.default.red("✗"),
20712
20781
  warn: import_picocolors3.default.yellow("⚠"),
20713
20782
  info: import_picocolors3.default.blue("ℹ"),
20783
+ working: import_picocolors3.default.cyan("◌"),
20714
20784
  arrow: import_picocolors3.default.dim("→"),
20715
20785
  bullet: import_picocolors3.default.dim("•"),
20716
20786
  sparkle: "✨",
20717
20787
  package: "\uD83D\uDCE6",
20718
20788
  search: "\uD83D\uDD0D",
20719
- wizard: "\uD83E\uDDD9",
20720
- share: "\uD83D\uDD17"
20789
+ generator: "\uD83E\uDDD9",
20790
+ share: "\uD83D\uDD17",
20791
+ verified: import_picocolors3.default.green("✓"),
20792
+ community: import_picocolors3.default.blue("◆"),
20793
+ github: import_picocolors3.default.dim("◎"),
20794
+ local: import_picocolors3.default.yellow("▸"),
20795
+ anonymous: import_picocolors3.default.dim("#"),
20796
+ modified: import_picocolors3.default.yellow("*")
20721
20797
  };
20798
+ function formatAspectLine(info) {
20799
+ const name = c3.aspect(info.name);
20800
+ const version2 = c3.version(`@${info.version}`);
20801
+ const scopes = c3.dim(`[${info.scopes.join(",")}]`);
20802
+ let source;
20803
+ switch (info.trust) {
20804
+ case "verified":
20805
+ source = icons2.verified + c3.dim(info.publisher ?? "");
20806
+ break;
20807
+ case "community":
20808
+ source = icons2.community + c3.dim(info.publisher ?? "");
20809
+ break;
20810
+ case "github":
20811
+ source = icons2.github + c3.dim("gh");
20812
+ break;
20813
+ case "local":
20814
+ source = icons2.local + c3.dim("local");
20815
+ break;
20816
+ default: {
20817
+ const spec = info.specifier ?? "";
20818
+ const hashPart = spec.startsWith("blake3:") ? spec.slice(7, 13) : spec.startsWith("hash:") ? spec.slice(5, 11) : "";
20819
+ source = hashPart ? icons2.anonymous + c3.dim(hashPart) : c3.dim("registry");
20820
+ }
20821
+ }
20822
+ const modified = info.isModified ? ` ${icons2.modified}` : "";
20823
+ const tagline = info.tagline ? c3.muted(` — ${info.tagline}`) : "";
20824
+ return ` ${name}${version2} ${scopes} ${source}${modified}${tagline}`;
20825
+ }
20722
20826
 
20723
20827
  // src/utils/paths.ts
20724
20828
  import { homedir } from "node:os";
@@ -20735,7 +20839,11 @@ async function findProjectRoot() {
20735
20839
  return cachedProjectRoot || null;
20736
20840
  let dir = process.cwd();
20737
20841
  const root = "/";
20842
+ const home = homedir();
20738
20843
  while (dir !== root) {
20844
+ if (dir === home) {
20845
+ break;
20846
+ }
20739
20847
  try {
20740
20848
  await stat(join(dir, PROJECT_ASPECTS_DIR_NAME));
20741
20849
  cachedProjectRoot = dir;
@@ -20850,6 +20958,22 @@ async function listAllInstalledAspects(projectRoot) {
20850
20958
  return globalAspects;
20851
20959
  }
20852
20960
  }
20961
+ async function findInstalledAspect(name, projectRoot) {
20962
+ const results = [];
20963
+ if (projectRoot) {
20964
+ try {
20965
+ const projectAspect = await getInstalledAspect(name, "project", projectRoot);
20966
+ if (projectAspect) {
20967
+ results.push({ scope: "project", ...projectAspect });
20968
+ }
20969
+ } catch {}
20970
+ }
20971
+ const globalAspect = await getInstalledAspect(name, "global");
20972
+ if (globalAspect) {
20973
+ results.push({ scope: "global", ...globalAspect });
20974
+ }
20975
+ return results;
20976
+ }
20853
20977
  async function getRegistryUrl() {
20854
20978
  const config2 = await readConfig();
20855
20979
  return resolveRegistryUrl(process.env.ASPECTS_REGISTRY_URL, config2.settings.registryUrl);
@@ -20880,9 +21004,50 @@ async function clearAuth() {
20880
21004
  delete config2.auth;
20881
21005
  await writeConfig(config2);
20882
21006
  }
21007
+ async function getDefaultHandle() {
21008
+ const auth = await getAuth();
21009
+ return auth?.defaultHandle ?? null;
21010
+ }
21011
+ async function getHandles() {
21012
+ const auth = await getAuth();
21013
+ return auth?.handles ?? [];
21014
+ }
21015
+ async function setDefaultHandle(handle) {
21016
+ const config2 = await readConfig();
21017
+ if (!config2.auth) {
21018
+ throw new Error("Not logged in");
21019
+ }
21020
+ const hasHandle = config2.auth.handles.some((h3) => h3.name === handle);
21021
+ if (!hasHandle) {
21022
+ throw new Error(`You don't have access to handle @${handle}`);
21023
+ }
21024
+ config2.auth.handles = config2.auth.handles.map((h3) => ({
21025
+ ...h3,
21026
+ default: h3.name === handle
21027
+ }));
21028
+ config2.auth.defaultHandle = handle;
21029
+ await writeConfig(config2);
21030
+ }
21031
+ async function updateHandles(handles) {
21032
+ const config2 = await readConfig();
21033
+ if (!config2.auth) {
21034
+ throw new Error("Not logged in");
21035
+ }
21036
+ config2.auth.handles = handles;
21037
+ const defaultStillExists = handles.some((h3) => h3.name === config2.auth.defaultHandle);
21038
+ if (!defaultStillExists) {
21039
+ const newDefault = handles.find((h3) => h3.default) ?? handles[0];
21040
+ config2.auth.defaultHandle = newDefault?.name ?? "";
21041
+ }
21042
+ await writeConfig(config2);
21043
+ }
21044
+ async function hasHandlePermission(handle) {
21045
+ const handles = await getHandles();
21046
+ return handles.some((h3) => h3.name === handle);
21047
+ }
20883
21048
 
20884
21049
  // src/lib/installer.ts
20885
- import { mkdir as mkdir2, writeFile as writeFile2, readFile as readFile3, stat as stat2, access } from "node:fs/promises";
21050
+ import { mkdir as mkdir2, writeFile as writeFile2, stat as stat2, access } from "node:fs/promises";
20886
21051
  import { join as join3, dirname } from "node:path";
20887
21052
 
20888
21053
  // node_modules/ofetch/dist/node.mjs
@@ -21678,7 +21843,7 @@ async function searchAspects(params) {
21678
21843
  async function publishAspect(aspect) {
21679
21844
  return apiFetch("/aspects", {
21680
21845
  method: "POST",
21681
- body: { aspect },
21846
+ body: aspect,
21682
21847
  auth: true
21683
21848
  });
21684
21849
  }
@@ -21712,6 +21877,26 @@ async function pollDeviceAuth(deviceCode, codeVerifier) {
21712
21877
  parseResponse: JSON.parse
21713
21878
  });
21714
21879
  }
21880
+ async function getAccount() {
21881
+ return apiFetch("/account", { auth: true });
21882
+ }
21883
+ async function claimHandle(name, displayName) {
21884
+ return apiFetch("/handles", {
21885
+ method: "POST",
21886
+ body: { name, display_name: displayName },
21887
+ auth: true
21888
+ });
21889
+ }
21890
+ async function checkHandleAvailability(name) {
21891
+ return apiFetch(`/handles/${encodeURIComponent(name)}/available`);
21892
+ }
21893
+ async function setDefaultHandleApi(handle) {
21894
+ await apiFetch("/account/default-handle", {
21895
+ method: "PUT",
21896
+ body: { handle },
21897
+ auth: true
21898
+ });
21899
+ }
21715
21900
 
21716
21901
  // src/lib/registry.ts
21717
21902
  var FALLBACK_REGISTRY_URL = "https://raw.githubusercontent.com/aimorphist/aspects/main/registry/index.json";
@@ -23258,12 +23443,17 @@ async function installFromRegistryApi(name, version2, options) {
23258
23443
  await mkdir2(aspectDir, { recursive: true });
23259
23444
  const content = JSON.stringify(aspect, null, 2);
23260
23445
  await writeFile2(join3(aspectDir, ASPECT_FILENAME), content);
23261
- const hash2 = versionData.blake3 || blake3Hash(content);
23446
+ const hash2 = versionData.blake3 || blake3HashAspect(aspect);
23447
+ const publisher = aspect.publisher;
23448
+ const trust = publisher ? "community" : undefined;
23262
23449
  await addInstalledAspect(name, {
23263
23450
  version: aspect.version,
23264
- source: "registry",
23265
23451
  installedAt: new Date().toISOString(),
23266
- blake3: hash2
23452
+ blake3: hash2,
23453
+ source: "registry",
23454
+ trust: trust ?? "community",
23455
+ publisher,
23456
+ specifier: options?.specifier ?? (publisher ? `${publisher}/${name}` : name)
23267
23457
  }, scope, projectRoot);
23268
23458
  return { success: true, aspect, source: "registry" };
23269
23459
  }
@@ -23328,12 +23518,17 @@ async function installFromRegistryLegacy(name, version2, options) {
23328
23518
  const aspectDir = getAspectPath(name, scope, projectRoot);
23329
23519
  await mkdir2(aspectDir, { recursive: true });
23330
23520
  await writeFile2(join3(aspectDir, ASPECT_FILENAME), content);
23331
- const hash2 = blake3Hash(content);
23521
+ const hash2 = blake3HashAspect(aspect);
23522
+ const publisher = aspect.publisher;
23523
+ const trust = registryAspect.metadata.trust ?? "community";
23332
23524
  await addInstalledAspect(name, {
23333
23525
  version: aspect.version,
23334
- source: "registry",
23335
23526
  installedAt: new Date().toISOString(),
23336
- blake3: hash2
23527
+ blake3: hash2,
23528
+ source: "registry",
23529
+ trust,
23530
+ publisher,
23531
+ specifier: options?.specifier ?? (publisher ? `${publisher}/${name}` : name)
23337
23532
  }, scope, projectRoot);
23338
23533
  return { success: true, aspect, source: "registry" };
23339
23534
  }
@@ -23370,11 +23565,12 @@ async function installFromGitHub(owner, repo, ref, options) {
23370
23565
  parseResult.warnings.forEach((w4) => log.warn(w4));
23371
23566
  }
23372
23567
  const aspect = parseResult.aspect;
23568
+ const hash2 = blake3HashAspect(aspect);
23373
23569
  if (!options?.force) {
23374
23570
  const existing = await getInstalledAspect(aspect.name, scope, projectRoot);
23375
- if (existing && existing.source === "github" && existing.githubRef === targetRef) {
23571
+ if (existing && existing.source === "github" && existing.githubRef === `${owner}/${repo}@${targetRef}`) {
23376
23572
  const existingAspect = await loadAspectFromPath(getAspectPath(aspect.name, scope, projectRoot));
23377
- if (existingAspect && existing.blake3 === blake3Hash(content)) {
23573
+ if (existingAspect && existing.blake3 === hash2) {
23378
23574
  return { success: true, aspect: existingAspect, source: "github", alreadyInstalled: true };
23379
23575
  }
23380
23576
  }
@@ -23383,13 +23579,15 @@ async function installFromGitHub(owner, repo, ref, options) {
23383
23579
  const aspectDir = getAspectPath(aspect.name, scope, projectRoot);
23384
23580
  await mkdir2(aspectDir, { recursive: true });
23385
23581
  await writeFile2(join3(aspectDir, ASPECT_FILENAME), content);
23386
- const hash2 = blake3Hash(content);
23582
+ const specifier = options?.specifier ?? `github:${owner}/${repo}@${targetRef}`;
23387
23583
  await addInstalledAspect(aspect.name, {
23388
23584
  version: aspect.version,
23389
- source: "github",
23390
23585
  installedAt: new Date().toISOString(),
23391
23586
  blake3: hash2,
23392
- githubRef: targetRef
23587
+ source: "github",
23588
+ trust: "github",
23589
+ githubRef: `${owner}/${repo}@${targetRef}`,
23590
+ specifier
23393
23591
  }, scope, projectRoot);
23394
23592
  return { success: true, aspect, source: "github" };
23395
23593
  }
@@ -23423,22 +23621,23 @@ async function installFromLocal(path, options) {
23423
23621
  parseResult.warnings.forEach((w4) => log.warn(w4));
23424
23622
  }
23425
23623
  const aspect = parseResult.aspect;
23426
- const content = await readFile3(filePath, "utf-8");
23427
- const hash2 = blake3Hash(content);
23624
+ const hash2 = blake3HashAspect(aspect);
23428
23625
  const scope = options?.scope ?? "global";
23429
23626
  const projectRoot = options?.projectRoot;
23430
23627
  if (!options?.force) {
23431
23628
  const existing = await getInstalledAspect(aspect.name, scope, projectRoot);
23432
- if (existing && existing.path === aspectDir && existing.blake3 === hash2) {
23629
+ if (existing && existing.localPath === aspectDir && existing.blake3 === hash2) {
23433
23630
  return { success: true, aspect, source: "local", alreadyInstalled: true };
23434
23631
  }
23435
23632
  }
23436
23633
  await addInstalledAspect(aspect.name, {
23437
23634
  version: aspect.version,
23438
- source: "local",
23439
23635
  installedAt: new Date().toISOString(),
23440
23636
  blake3: hash2,
23441
- path: aspectDir
23637
+ source: "local",
23638
+ trust: "local",
23639
+ localPath: aspectDir,
23640
+ specifier: options?.specifier ?? path
23442
23641
  }, scope, projectRoot);
23443
23642
  return { success: true, aspect, source: "local" };
23444
23643
  }
@@ -23473,9 +23672,11 @@ async function installFromHash(hash2, options) {
23473
23672
  await writeFile2(join3(aspectDir, ASPECT_FILENAME), content);
23474
23673
  await addInstalledAspect(aspect.name, {
23475
23674
  version: aspect.version,
23476
- source: "registry",
23477
23675
  installedAt: new Date().toISOString(),
23478
- blake3: versionData.blake3 || blake3Hash(content)
23676
+ blake3: versionData.blake3 || blake3HashAspect(aspect),
23677
+ source: "registry",
23678
+ trust: "community",
23679
+ specifier: options?.specifier ?? `blake3:${hash2}`
23479
23680
  }, scope, projectRoot);
23480
23681
  return { success: true, aspect, source: "registry" };
23481
23682
  }
@@ -23490,8 +23691,8 @@ async function loadAspectFromPath(aspectDir) {
23490
23691
  // src/lib/resolver.ts
23491
23692
  import { resolve, isAbsolute } from "node:path";
23492
23693
  function parseInstallSpec(spec) {
23493
- if (spec.startsWith("hash:")) {
23494
- const hash2 = spec.slice(5);
23694
+ if (spec.startsWith("blake3:") || spec.startsWith("hash:")) {
23695
+ const hash2 = spec.startsWith("blake3:") ? spec.slice(7) : spec.slice(5);
23495
23696
  if (hash2.length < 16) {
23496
23697
  throw new Error(`Invalid hash spec: ${spec}. Hash must be at least 16 characters.`);
23497
23698
  }
@@ -23515,33 +23716,49 @@ function parseInstallSpec(spec) {
23515
23716
  const absolutePath = isAbsolute(spec) ? spec : resolve(process.cwd(), spec);
23516
23717
  return { type: "local", path: absolutePath };
23517
23718
  }
23719
+ if (spec.includes("aspects.sh/")) {
23720
+ const aspectsMatch = spec.match(/aspects\.sh\/aspects\/([a-z0-9-]+)/i);
23721
+ if (aspectsMatch?.[1]) {
23722
+ return { type: "registry", name: aspectsMatch[1] };
23723
+ }
23724
+ const hashMatch = spec.match(/aspects\.sh\/a\/([A-Za-z0-9]{16,44})/);
23725
+ if (hashMatch?.[1]) {
23726
+ return { type: "hash", hash: hashMatch[1] };
23727
+ }
23728
+ throw new Error(`Invalid aspects.sh URL: ${spec}`);
23729
+ }
23518
23730
  let name;
23731
+ let publisher;
23519
23732
  let version2;
23520
- if (spec.startsWith("@")) {
23521
- const lastAtIndex = spec.lastIndexOf("@");
23522
- if (lastAtIndex > 0 && spec.indexOf("/") < lastAtIndex) {
23523
- name = spec.slice(0, lastAtIndex);
23524
- version2 = spec.slice(lastAtIndex + 1);
23525
- } else {
23526
- name = spec;
23527
- }
23733
+ const atIndex = spec.lastIndexOf("@");
23734
+ let nameWithPublisher;
23735
+ if (atIndex > 0) {
23736
+ nameWithPublisher = spec.slice(0, atIndex);
23737
+ version2 = spec.slice(atIndex + 1);
23528
23738
  } else {
23529
- const atIndex = spec.indexOf("@");
23530
- if (atIndex > 0) {
23531
- name = spec.slice(0, atIndex);
23532
- version2 = spec.slice(atIndex + 1);
23533
- } else {
23534
- name = spec;
23739
+ nameWithPublisher = spec;
23740
+ }
23741
+ const slashIndex = nameWithPublisher.indexOf("/");
23742
+ if (slashIndex > 0) {
23743
+ publisher = nameWithPublisher.slice(0, slashIndex);
23744
+ if (publisher.startsWith("@")) {
23745
+ publisher = publisher.slice(1);
23535
23746
  }
23747
+ name = nameWithPublisher.slice(slashIndex + 1);
23748
+ if (!name) {
23749
+ throw new Error(`Invalid spec: ${spec}. Expected publisher/name format.`);
23750
+ }
23751
+ } else {
23752
+ name = nameWithPublisher;
23536
23753
  }
23537
- return { type: "registry", name, version: version2 };
23754
+ return { type: "registry", name, publisher, version: version2 };
23538
23755
  }
23539
23756
 
23540
23757
  // src/commands/set.ts
23541
23758
  async function loadSet(name) {
23542
23759
  try {
23543
23760
  const setPath = join4(getSetsDir(), name, "set.json");
23544
- const content = await readFile4(setPath, "utf-8");
23761
+ const content = await readFile3(setPath, "utf-8");
23545
23762
  return JSON.parse(content);
23546
23763
  } catch {
23547
23764
  return null;
@@ -23586,7 +23803,7 @@ var createCommand = defineCommand({
23586
23803
  },
23587
23804
  aspects: {
23588
23805
  type: "positional",
23589
- description: "Aspects to include (optional, launches wizard if omitted)",
23806
+ description: "Aspects to include (optional, launches generator if omitted)",
23590
23807
  required: false
23591
23808
  }
23592
23809
  },
@@ -23991,7 +24208,28 @@ var CATEGORY_OPTIONS = [
23991
24208
  var create_default = defineCommand({
23992
24209
  meta: {
23993
24210
  name: "create",
23994
- description: "Create a new aspect interactively"
24211
+ description: `Create a new aspect interactively.
24212
+
24213
+ The generator guides you through:
24214
+ 1. Name & identity (slug, display name, tagline)
24215
+ 2. Category selection (assistant, roleplay, creative, etc.)
24216
+ 3. Tags for discovery
24217
+ 4. Voice hints (speed, emotions)
24218
+ 5. Directives & Instructions
24219
+
24220
+ Directives vs Instructions:
24221
+ Directives Strict MUST-follow rules with priority (high/medium/low)
24222
+ Emphasized across all LLM models via XML, bold, repetition
24223
+ Instructions Softer guidance and preferences, not strictly enforced
24224
+
24225
+ Examples:
24226
+ aspects create Create in current directory
24227
+ aspects create my-aspect Create in ./my-aspect/
24228
+ aspects create ~/aspects/new Create at specific path
24229
+
24230
+ If run inside the aspects registry repo, automatically offers to:
24231
+ - Add to registry/index.json
24232
+ - Commit and push changes`
23995
24233
  },
23996
24234
  args: {
23997
24235
  path: {
@@ -24041,7 +24279,7 @@ var create_default = defineCommand({
24041
24279
  while (!aspectName) {
24042
24280
  const nameInput = await he2({
24043
24281
  message: "Aspect name (slug)",
24044
- placeholder: "my-wizard",
24282
+ placeholder: "my-aspect",
24045
24283
  validate: (value) => {
24046
24284
  if (!value)
24047
24285
  return "Name is required";
@@ -24079,7 +24317,7 @@ var create_default = defineCommand({
24079
24317
  const answers = await Ce({
24080
24318
  displayName: () => he2({
24081
24319
  message: "Display name",
24082
- placeholder: "My Wizard",
24320
+ placeholder: "My Aspect",
24083
24321
  validate: (value) => {
24084
24322
  if (!value)
24085
24323
  return "Display name is required";
@@ -24087,7 +24325,7 @@ var create_default = defineCommand({
24087
24325
  }),
24088
24326
  tagline: () => he2({
24089
24327
  message: "Tagline (one-liner description)",
24090
- placeholder: "A wise and quirky wizard who loves riddles",
24328
+ placeholder: "A helpful assistant with a unique personality",
24091
24329
  validate: (value) => {
24092
24330
  if (!value)
24093
24331
  return "Tagline is required";
@@ -24126,17 +24364,17 @@ var create_default = defineCommand({
24126
24364
  message: "Voice speed",
24127
24365
  options: [
24128
24366
  { value: "normal", label: "Normal" },
24129
- { value: "slow", label: "Slow deliberate, thoughtful" },
24130
- { value: "fast", label: "Fast energetic, excited" }
24367
+ { value: "slow", label: "Slow - deliberate, thoughtful" },
24368
+ { value: "fast", label: "Fast - energetic, excited" }
24131
24369
  ],
24132
24370
  initialValue: "normal"
24133
24371
  }),
24134
24372
  promptStyle: () => ve2({
24135
24373
  message: "Prompt template",
24136
24374
  options: [
24137
- { value: "character", label: "Character roleplay a persona" },
24138
- { value: "assistant", label: "Assistant helpful AI style" },
24139
- { value: "blank", label: "Blank start from scratch" }
24375
+ { value: "character", label: "Character - roleplay a persona" },
24376
+ { value: "assistant", label: "Assistant - helpful AI style" },
24377
+ { value: "blank", label: "Blank - start from scratch" }
24140
24378
  ],
24141
24379
  initialValue: "character"
24142
24380
  })
@@ -24151,11 +24389,11 @@ var create_default = defineCommand({
24151
24389
  M2.message(`
24152
24390
  \uD83D\uDCCB Directives & Instructions
24153
24391
 
24154
- Directives are MUST-follow rules they get special emphasis
24392
+ Directives are MUST-follow rules - they get special emphasis
24155
24393
  across all LLM models (bold, XML tags, repetition).
24156
24394
  Example: "Never break character under any circumstances"
24157
24395
 
24158
- Instructions are general guidance softer preferences for
24396
+ Instructions are general guidance - softer preferences for
24159
24397
  how the AI should behave.
24160
24398
  Example: "Prefer shorter responses when possible"
24161
24399
 
@@ -24188,7 +24426,7 @@ Keep it light! A few well-crafted rules beat many vague ones.
24188
24426
  },
24189
24427
  {
24190
24428
  value: "done",
24191
- label: hasAny ? "Done finish creating aspect" : "Skip finish without adding any"
24429
+ label: hasAny ? "Done - finish creating aspect" : "Skip - finish without adding any"
24192
24430
  }
24193
24431
  ]
24194
24432
  });
@@ -24267,7 +24505,7 @@ Keep it light! A few well-crafted rules beat many vague ones.
24267
24505
  const aspect = {
24268
24506
  schemaVersion: 1,
24269
24507
  name: aspectName,
24270
- publisher: answers.author || "community",
24508
+ publisher: "anon-user",
24271
24509
  version: "1.0.0",
24272
24510
  displayName: answers.displayName,
24273
24511
  tagline: answers.tagline,
@@ -24346,11 +24584,11 @@ Keep it light! A few well-crafted rules beat many vague ones.
24346
24584
  M2.info(`
24347
24585
  Next: Open a PR at https://github.com/${GITHUB_REPO}/compare`);
24348
24586
  } catch {
24349
- M2.warn("Push failed you may need to set up your remote");
24587
+ M2.warn("Push failed - you may need to set up your remote");
24350
24588
  }
24351
24589
  }
24352
24590
  } catch {
24353
- M2.warn("Git commit failed you may need to commit manually");
24591
+ M2.warn("Git commit failed - you may need to commit manually");
24354
24592
  }
24355
24593
  }
24356
24594
  } else {
@@ -24387,7 +24625,7 @@ To submit to the registry:`);
24387
24625
  });
24388
24626
  async function addToRegistryIndex(repoRoot, name, displayName, tagline, category, publisher) {
24389
24627
  const indexPath = join5(repoRoot, INDEX_PATH);
24390
- const indexContent = await readFile5(indexPath, "utf-8");
24628
+ const indexContent = await readFile4(indexPath, "utf-8");
24391
24629
  const index = JSON.parse(indexContent);
24392
24630
  const now = new Date().toISOString();
24393
24631
  index.aspects[name] = {
@@ -24456,16 +24694,78 @@ ${tagline}
24456
24694
  }
24457
24695
  }
24458
24696
 
24697
+ // src/commands/init.ts
24698
+ import { existsSync } from "node:fs";
24699
+ import { mkdir as mkdir5 } from "node:fs/promises";
24700
+ import { join as join6 } from "node:path";
24701
+ var init_default = defineCommand({
24702
+ meta: {
24703
+ name: "init",
24704
+ description: "Initialize a project for local aspect installation"
24705
+ },
24706
+ args: {
24707
+ force: {
24708
+ type: "boolean",
24709
+ alias: "f",
24710
+ description: "Overwrite existing .aspects directory"
24711
+ }
24712
+ },
24713
+ async run({ args }) {
24714
+ const cwd = process.cwd();
24715
+ const aspectsDir = join6(cwd, PROJECT_ASPECTS_DIR_NAME);
24716
+ if (existsSync(aspectsDir) && !args.force) {
24717
+ M2.warn(`${c3.file(".aspects/")} already exists in this directory`);
24718
+ const overwrite = await ye2({
24719
+ message: "Reinitialize?"
24720
+ });
24721
+ if (pD2(overwrite) || !overwrite) {
24722
+ xe("Cancelled");
24723
+ process.exit(0);
24724
+ }
24725
+ }
24726
+ await mkdir5(aspectsDir, { recursive: true });
24727
+ console.log();
24728
+ console.log(`${icons2.success} Initialized ${c3.file(".aspects/")} in ${c3.muted(cwd)}`);
24729
+ console.log();
24730
+ console.log(` Now you can run ${c3.cmd("aspects add <name>")} to install locally.`);
24731
+ console.log();
24732
+ }
24733
+ });
24734
+ async function initProjectAspects() {
24735
+ const cwd = process.cwd();
24736
+ const aspectsDir = join6(cwd, PROJECT_ASPECTS_DIR_NAME);
24737
+ await mkdir5(aspectsDir, { recursive: true });
24738
+ return cwd;
24739
+ }
24740
+
24459
24741
  // src/commands/add.ts
24460
24742
  var add_default = defineCommand({
24461
24743
  meta: {
24462
24744
  name: "add",
24463
- description: "Install aspect(s) to your local library"
24745
+ description: `Install aspect(s) to your local library.
24746
+
24747
+ Sources:
24748
+ aspects add alaric From registry (aspects.sh)
24749
+ aspects add alaric@1.2.0 Specific version
24750
+ aspects add blake3:<hash> By content hash (from 'aspects share')
24751
+ aspects add github:user/repo From GitHub repository
24752
+ aspects add ./my-aspect From local path
24753
+
24754
+ Scope:
24755
+ By default, installs to ./.aspects/ if in a project, else ~/.aspects/
24756
+ Use -g/--global to always install to ~/.aspects/
24757
+ Use -p/--project to install locally (will prompt to init if needed)
24758
+
24759
+ Examples:
24760
+ aspects add alaric meditation-guide Install multiple aspects
24761
+ aspects add -g alaric Install globally
24762
+ aspects add -p alaric Install to project (init if needed)
24763
+ aspects add --force alaric Overwrite existing`
24464
24764
  },
24465
24765
  args: {
24466
24766
  specs: {
24467
24767
  type: "positional",
24468
- description: "Aspect name(s), github:user/repo, or ./path",
24768
+ description: "Aspect spec(s): name, name@version, blake3:<hash>, github:user/repo, or ./path",
24469
24769
  required: true
24470
24770
  },
24471
24771
  force: {
@@ -24476,6 +24776,11 @@ var add_default = defineCommand({
24476
24776
  type: "boolean",
24477
24777
  alias: "g",
24478
24778
  description: "Install to global scope (~/.aspects) instead of project"
24779
+ },
24780
+ project: {
24781
+ type: "boolean",
24782
+ alias: "p",
24783
+ description: "Install to project scope (init if needed)"
24479
24784
  }
24480
24785
  },
24481
24786
  async run({ args }) {
@@ -24484,6 +24789,24 @@ var add_default = defineCommand({
24484
24789
  let projectRoot;
24485
24790
  if (args.global) {
24486
24791
  scope = "global";
24792
+ } else if (args.project) {
24793
+ projectRoot = await findProjectRoot() || undefined;
24794
+ if (!projectRoot) {
24795
+ console.log();
24796
+ const init2 = await ye2({
24797
+ message: `No ${c3.file(".aspects/")} found. Initialize project here?`
24798
+ });
24799
+ if (pD2(init2) || !init2) {
24800
+ console.log(c3.muted(" Falling back to global scope"));
24801
+ scope = "global";
24802
+ } else {
24803
+ projectRoot = await initProjectAspects();
24804
+ console.log(`${icons2.success} Initialized ${c3.file(".aspects/")}`);
24805
+ scope = "project";
24806
+ }
24807
+ } else {
24808
+ scope = "project";
24809
+ }
24487
24810
  } else {
24488
24811
  projectRoot = await findProjectRoot() || undefined;
24489
24812
  scope = projectRoot ? "project" : "global";
@@ -24500,7 +24823,12 @@ var add_default = defineCommand({
24500
24823
  results.push({ spec: specStr, success: false, error: err.message });
24501
24824
  continue;
24502
24825
  }
24503
- const result = await installAspect(spec, { force: !!args.force, scope, projectRoot });
24826
+ const result = await installAspect(spec, {
24827
+ force: !!args.force,
24828
+ scope,
24829
+ projectRoot,
24830
+ specifier: specStr
24831
+ });
24504
24832
  if (!result.success) {
24505
24833
  results.push({ spec: specStr, success: false, error: result.error });
24506
24834
  continue;
@@ -24549,18 +24877,33 @@ var add_default = defineCommand({
24549
24877
  });
24550
24878
 
24551
24879
  // src/lib/aspect-loader.ts
24552
- import { join as join6 } from "node:path";
24880
+ import { join as join7 } from "node:path";
24553
24881
  var ASPECT_FILENAME2 = "aspect.json";
24554
24882
  var LEGACY_FILENAME2 = "aspect.yaml";
24883
+ async function findAndLoadAspect(name) {
24884
+ const projectRoot = await findProjectRoot() || undefined;
24885
+ const installed = await findInstalledAspect(name, projectRoot);
24886
+ if (installed.length === 0)
24887
+ return null;
24888
+ const match = installed.find((i2) => i2.scope === "project") || installed[0];
24889
+ const aspect = await loadInstalledAspect(name, match.scope, projectRoot);
24890
+ if (!aspect)
24891
+ return null;
24892
+ return {
24893
+ aspect,
24894
+ scope: match.scope,
24895
+ meta: match
24896
+ };
24897
+ }
24555
24898
  async function loadInstalledAspect(name, scope = "global", projectRoot) {
24556
24899
  const installed = await getInstalledAspect(name, scope, projectRoot);
24557
24900
  if (!installed)
24558
24901
  return null;
24559
- const aspectDir = installed.path ?? getAspectPath(name, scope, projectRoot);
24560
- const jsonResult = await parseAspectFile(join6(aspectDir, ASPECT_FILENAME2));
24902
+ const aspectDir = installed.localPath ?? getAspectPath(name, scope, projectRoot);
24903
+ const jsonResult = await parseAspectFile(join7(aspectDir, ASPECT_FILENAME2));
24561
24904
  if (jsonResult.success)
24562
24905
  return jsonResult.aspect;
24563
- const yamlResult = await parseAspectFile(join6(aspectDir, LEGACY_FILENAME2));
24906
+ const yamlResult = await parseAspectFile(join7(aspectDir, LEGACY_FILENAME2));
24564
24907
  return yamlResult.success ? yamlResult.aspect : null;
24565
24908
  }
24566
24909
 
@@ -24595,21 +24938,44 @@ var list_default = defineCommand({
24595
24938
  if (installed.length === 0) {
24596
24939
  console.log();
24597
24940
  console.log(c3.muted(" No aspects installed."));
24598
- console.log(c3.muted(` Run ${c3.highlight("aspects install <name>")} to get started.`));
24941
+ console.log(c3.muted(` Run ${c3.highlight("aspects add <name>")} to get started.`));
24599
24942
  console.log();
24600
24943
  return;
24601
24944
  }
24945
+ const grouped = new Map;
24946
+ for (const item of installed) {
24947
+ const existing = grouped.get(item.blake3);
24948
+ if (existing) {
24949
+ if (!existing.scopes.includes(item.scope)) {
24950
+ existing.scopes.push(item.scope);
24951
+ }
24952
+ } else {
24953
+ const aspect = await loadInstalledAspect(item.name, item.scope, projectRoot);
24954
+ let isModified = false;
24955
+ if (aspect) {
24956
+ const currentHash = blake3HashAspect(aspect);
24957
+ isModified = currentHash !== item.blake3;
24958
+ }
24959
+ const trust = item.trust ?? (item.source === "github" ? "github" : item.source === "local" ? "local" : "community");
24960
+ grouped.set(item.blake3, {
24961
+ name: item.name,
24962
+ version: item.version,
24963
+ blake3: item.blake3,
24964
+ scopes: [item.scope],
24965
+ trust,
24966
+ publisher: item.publisher,
24967
+ specifier: item.specifier,
24968
+ tagline: aspect?.tagline,
24969
+ isModified
24970
+ });
24971
+ }
24972
+ }
24602
24973
  console.log();
24603
24974
  console.log(c3.bold(`${icons2.package} Installed aspects`));
24604
24975
  console.log();
24605
- for (const item of installed) {
24606
- const aspect = await loadInstalledAspect(item.name, item.scope, projectRoot);
24607
- const scopeLabel = item.scope === "project" ? c3.dim(" [project]") : c3.dim(" [global]");
24608
- const sourceLabel = item.source === "local" ? c3.dim(" (local)") : item.source === "github" ? c3.dim(" (github)") : "";
24609
- const name = c3.aspect(item.name);
24610
- const version2 = c3.version(`@${item.version}`);
24611
- const tagline = aspect?.tagline ? c3.muted(` — ${aspect.tagline}`) : "";
24612
- console.log(` ${name}${version2}${scopeLabel}${sourceLabel}${tagline}`);
24976
+ for (const item of grouped.values()) {
24977
+ item.scopes.sort((a2, b4) => a2 === "project" ? -1 : 1);
24978
+ console.log(formatAspectLine(item));
24613
24979
  }
24614
24980
  console.log();
24615
24981
  }
@@ -24619,7 +24985,19 @@ var list_default = defineCommand({
24619
24985
  var search_default = defineCommand({
24620
24986
  meta: {
24621
24987
  name: "search",
24622
- description: "Search the aspect registry"
24988
+ description: `Search the aspect registry.
24989
+
24990
+ Searches aspect names, display names, taglines, and tags.
24991
+
24992
+ Examples:
24993
+ aspects search wizard Find aspects matching "wizard"
24994
+ aspects search List all aspects
24995
+ aspects search --category roleplay Filter by category
24996
+ aspects search --trust verified Only verified aspects
24997
+
24998
+ Categories: assistant, roleplay, creative, productivity, education, gaming, spiritual, pundit
24999
+
25000
+ For advanced filtering, use 'aspects find' with boolean operators.`
24623
25001
  },
24624
25002
  args: {
24625
25003
  query: {
@@ -24952,7 +25330,7 @@ var find_default = defineCommand({
24952
25330
  if (results.some((r6) => r6.aspect.name === name && r6.source === "registry")) {
24953
25331
  continue;
24954
25332
  }
24955
- const aspectPath = aspectInfo.path || getAspectPath(name);
25333
+ const aspectPath = aspectInfo.localPath || getAspectPath(name);
24956
25334
  const parseResult = await parseAspectFile(`${aspectPath}/aspect.json`);
24957
25335
  if (!parseResult.success)
24958
25336
  continue;
@@ -25027,28 +25405,24 @@ var info_default = defineCommand({
25027
25405
  }
25028
25406
  },
25029
25407
  async run({ args }) {
25030
- const installed = await getInstalledAspect(args.name);
25031
- if (installed) {
25032
- const aspect = await loadInstalledAspect(args.name);
25033
- if (!aspect) {
25034
- log.error(`Failed to load aspect "${args.name}" — aspect.json may be corrupted`);
25035
- process.exit(1);
25036
- }
25408
+ const found = await findAndLoadAspect(args.name);
25409
+ if (found) {
25410
+ const { aspect, scope, meta: installMeta } = found;
25037
25411
  console.log();
25038
- console.log(`${c3.bold(aspect.displayName)} ${c3.muted("(")}${c3.aspect(aspect.name)}${c3.version(`@${aspect.version}`)}${c3.muted(")")}`);
25412
+ console.log(`${c3.bold(aspect.displayName)} ${c3.muted("(")}${c3.aspect(aspect.name)}${c3.version(`@${aspect.version}`)}${c3.muted(")")} ${c3.dim(`[${scope}]`)}`);
25039
25413
  console.log();
25040
25414
  console.log(` ${c3.italic(aspect.tagline)}`);
25041
25415
  console.log();
25042
- const meta3 = [];
25416
+ const displayMeta = [];
25043
25417
  if (aspect.publisher)
25044
- meta3.push(["Publisher", aspect.publisher]);
25418
+ displayMeta.push(["Publisher", aspect.publisher]);
25045
25419
  if (aspect.author)
25046
- meta3.push(["Author", aspect.author]);
25420
+ displayMeta.push(["Author", aspect.author]);
25047
25421
  if (aspect.license)
25048
- meta3.push(["License", aspect.license]);
25049
- meta3.push(["Source", installed.source]);
25050
- if (meta3.length > 0) {
25051
- for (const [label, value] of meta3) {
25422
+ displayMeta.push(["License", aspect.license]);
25423
+ displayMeta.push(["Source", installMeta.source]);
25424
+ if (displayMeta.length > 0) {
25425
+ for (const [label, value] of displayMeta) {
25052
25426
  console.log(` ${c3.label(label.padEnd(10))} ${c3.value(value)}`);
25053
25427
  }
25054
25428
  }
@@ -25162,31 +25536,79 @@ var remove_default = defineCommand({
25162
25536
  type: "boolean",
25163
25537
  alias: "g",
25164
25538
  description: "Remove from global scope (~/.aspects) instead of project"
25539
+ },
25540
+ project: {
25541
+ type: "boolean",
25542
+ alias: "p",
25543
+ description: "Remove from project scope only"
25544
+ },
25545
+ force: {
25546
+ type: "boolean",
25547
+ alias: "f",
25548
+ description: "Skip confirmation prompt"
25165
25549
  }
25166
25550
  },
25167
25551
  async run({ args }) {
25168
- let scope;
25169
- let projectRoot;
25170
- if (args.global) {
25171
- scope = "global";
25172
- } else {
25173
- projectRoot = await findProjectRoot() || undefined;
25174
- scope = projectRoot ? "project" : "global";
25175
- }
25176
- const installed = await getInstalledAspect(args.name, scope, projectRoot);
25177
- if (!installed) {
25552
+ const projectRoot = await findProjectRoot() || undefined;
25553
+ const found = await findInstalledAspect(args.name, projectRoot);
25554
+ if (found.length === 0) {
25178
25555
  log.error(`Aspect "${args.name}" is not installed`);
25179
25556
  process.exit(1);
25180
25557
  }
25181
- await removeInstalledAspect(args.name, scope, projectRoot);
25182
- if (installed.source === "registry" || installed.source === "github") {
25183
- const aspectDir = getAspectPath(args.name, scope, projectRoot);
25184
- try {
25185
- await rm(aspectDir, { recursive: true });
25186
- } catch {}
25558
+ let toRemove = found;
25559
+ if (args.global && !args.project) {
25560
+ toRemove = found.filter((f4) => f4.scope === "global");
25561
+ if (toRemove.length === 0) {
25562
+ log.error(`Aspect "${args.name}" is not installed globally`);
25563
+ process.exit(1);
25564
+ }
25565
+ } else if (args.project && !args.global) {
25566
+ toRemove = found.filter((f4) => f4.scope === "project");
25567
+ if (toRemove.length === 0) {
25568
+ log.error(`Aspect "${args.name}" is not installed in project scope`);
25569
+ process.exit(1);
25570
+ }
25571
+ }
25572
+ if (toRemove.length > 1 && !args.global && !args.project) {
25573
+ const choice = await ve2({
25574
+ message: `"${args.name}" is installed in multiple scopes. Which to remove?`,
25575
+ options: [
25576
+ ...toRemove.map((f4) => ({
25577
+ value: f4.scope,
25578
+ label: `${f4.scope} (${f4.version})`
25579
+ })),
25580
+ { value: "both", label: "Both" }
25581
+ ]
25582
+ });
25583
+ if (pD2(choice)) {
25584
+ xe("Cancelled");
25585
+ process.exit(0);
25586
+ }
25587
+ if (choice === "both") {} else {
25588
+ toRemove = toRemove.filter((f4) => f4.scope === choice);
25589
+ }
25590
+ }
25591
+ if (!args.force && toRemove.length > 0) {
25592
+ const first = toRemove[0];
25593
+ const scopeDesc = toRemove.length === 1 ? `from ${c3.dim(first.scope)} scope` : `from ${toRemove.map((f4) => c3.dim(f4.scope)).join(" and ")} scopes`;
25594
+ const confirmed = await ye2({
25595
+ message: `Remove ${c3.aspect(args.name)}${c3.version(`@${first.version}`)} ${scopeDesc}?`
25596
+ });
25597
+ if (pD2(confirmed) || !confirmed) {
25598
+ xe("Cancelled");
25599
+ process.exit(0);
25600
+ }
25601
+ }
25602
+ for (const install of toRemove) {
25603
+ await removeInstalledAspect(args.name, install.scope, projectRoot);
25604
+ if (install.source === "registry" || install.source === "github") {
25605
+ const aspectDir = getAspectPath(args.name, install.scope, projectRoot);
25606
+ try {
25607
+ await rm(aspectDir, { recursive: true });
25608
+ } catch {}
25609
+ }
25610
+ console.log(`${icons2.success} Removed ${c3.aspect(args.name)}${c3.version(`@${install.version}`)} ${c3.dim(`[${install.scope}]`)}`);
25187
25611
  }
25188
- console.log();
25189
- console.log(`${icons2.success} Removed ${c3.aspect(args.name)}${c3.version(`@${installed.version}`)}`);
25190
25612
  console.log();
25191
25613
  }
25192
25614
  });
@@ -25234,22 +25656,22 @@ var update_default = defineCommand({
25234
25656
  let updated = 0;
25235
25657
  for (const aspect of toCheck) {
25236
25658
  if (aspect.source === "local") {
25237
- console.log(` ${c3.aspect(aspect.name)} ${c3.muted(" local install, skipping")}`);
25659
+ console.log(` ${c3.aspect(aspect.name)} ${c3.muted("- local install, skipping")}`);
25238
25660
  continue;
25239
25661
  }
25240
25662
  if (aspect.source === "github") {
25241
- console.log(` ${c3.aspect(aspect.name)} ${c3.muted(" github install, use")} ${c3.highlight("aspects install github:...")} ${c3.muted("to update")}`);
25663
+ console.log(` ${c3.aspect(aspect.name)} ${c3.muted("- github install, use")} ${c3.highlight("aspects install github:...")} ${c3.muted("to update")}`);
25242
25664
  continue;
25243
25665
  }
25244
25666
  let registryInfo;
25245
25667
  try {
25246
25668
  registryInfo = await getRegistryAspect(aspect.name);
25247
25669
  } catch {
25248
- console.log(` ${c3.aspect(aspect.name)} ${c3.error(" failed to check registry")}`);
25670
+ console.log(` ${c3.aspect(aspect.name)} ${c3.error("- failed to check registry")}`);
25249
25671
  continue;
25250
25672
  }
25251
25673
  if (!registryInfo) {
25252
- console.log(` ${c3.aspect(aspect.name)} ${c3.warn(" not found in registry")}`);
25674
+ console.log(` ${c3.aspect(aspect.name)} ${c3.warn("- not found in registry")}`);
25253
25675
  continue;
25254
25676
  }
25255
25677
  const currentVersion = aspect.version;
@@ -25296,12 +25718,30 @@ var update_default = defineCommand({
25296
25718
  });
25297
25719
 
25298
25720
  // src/commands/validate.ts
25299
- import { readFile as readFile6, stat as stat4 } from "node:fs/promises";
25300
- import { join as join7 } from "node:path";
25721
+ import { readFile as readFile5, stat as stat4 } from "node:fs/promises";
25722
+ import { join as join8 } from "node:path";
25301
25723
  var validate_default = defineCommand({
25302
25724
  meta: {
25303
25725
  name: "validate",
25304
- description: "Validate an aspect.json file against the schema"
25726
+ description: `Validate an aspect.json file against the schema.
25727
+
25728
+ Checks:
25729
+ - Required fields (name, displayName, tagline, category, prompt)
25730
+ - Field length limits
25731
+ - Category is valid
25732
+ - Directive/instruction structure
25733
+ - Mode references valid directives
25734
+
25735
+ Examples:
25736
+ aspects validate Validate in current directory
25737
+ aspects validate ./my-aspect Validate specific path
25738
+ aspects validate --strict Stricter checks
25739
+ aspects validate --security Scan for prompt injection patterns
25740
+
25741
+ Security scan flags patterns like:
25742
+ - "ignore previous instructions"
25743
+ - Requests for passwords or financial info
25744
+ - Known jailbreak attempts`
25305
25745
  },
25306
25746
  args: {
25307
25747
  path: {
@@ -25326,15 +25766,28 @@ var validate_default = defineCommand({
25326
25766
  try {
25327
25767
  const stats = await stat4(aspectPath);
25328
25768
  if (stats.isDirectory()) {
25329
- aspectPath = join7(aspectPath, "aspect.json");
25769
+ aspectPath = join8(aspectPath, "aspect.json");
25330
25770
  }
25331
25771
  } catch {
25332
- M2.error(`Path not found: ${aspectPath}`);
25333
- process.exit(1);
25772
+ if (args.path) {
25773
+ const projectRoot = await findProjectRoot() || undefined;
25774
+ const installed = await findInstalledAspect(args.path, projectRoot);
25775
+ if (installed.length > 0) {
25776
+ const match = installed.find((i2) => i2.scope === "project") || installed[0];
25777
+ aspectPath = join8(getAspectPath(args.path, match.scope, projectRoot), "aspect.json");
25778
+ M2.info(`Found installed: ${c3.aspect(args.path)} ${c3.dim(`[${match.scope}]`)}`);
25779
+ } else {
25780
+ M2.error(`Path not found: ${aspectPath}`);
25781
+ process.exit(1);
25782
+ }
25783
+ } else {
25784
+ M2.error(`Path not found: ${aspectPath}`);
25785
+ process.exit(1);
25786
+ }
25334
25787
  }
25335
25788
  let content;
25336
25789
  try {
25337
- content = await readFile6(aspectPath, "utf-8");
25790
+ content = await readFile5(aspectPath, "utf-8");
25338
25791
  } catch {
25339
25792
  M2.error(`Cannot read file: ${aspectPath}`);
25340
25793
  process.exit(1);
@@ -25420,7 +25873,7 @@ var validate_default = defineCommand({
25420
25873
  M2.info("Checks:");
25421
25874
  for (const check2 of checks3) {
25422
25875
  const icon = check2.passed ? "✓" : "✗";
25423
- const msg = check2.message ? ` ${check2.message}` : "";
25876
+ const msg = check2.message ? ` - ${check2.message}` : "";
25424
25877
  if (check2.passed) {
25425
25878
  M2.success(` ${icon} ${check2.label}${msg}`);
25426
25879
  } else {
@@ -25437,8 +25890,8 @@ var validate_default = defineCommand({
25437
25890
  });
25438
25891
 
25439
25892
  // src/commands/compile.ts
25440
- import { readFile as readFile7, writeFile as writeFile5, stat as stat5 } from "node:fs/promises";
25441
- import { join as join8 } from "node:path";
25893
+ import { readFile as readFile6, writeFile as writeFile5, stat as stat5 } from "node:fs/promises";
25894
+ import { join as join9 } from "node:path";
25442
25895
  function detectModelFamily(model) {
25443
25896
  const lower = model.toLowerCase();
25444
25897
  if (lower.includes("claude")) {
@@ -25455,28 +25908,45 @@ function detectModelFamily(model) {
25455
25908
  }
25456
25909
  return "unknown";
25457
25910
  }
25458
- function formatDirectivesForModel(directives, family) {
25911
+ function formatDirectivesForModel(directives, family, options) {
25459
25912
  if (directives.length === 0)
25460
25913
  return "";
25461
25914
  const isModern = family === "claude-modern" || family === "gpt-modern" || family === "unknown";
25915
+ const isReminder = options?.isReminder ?? false;
25462
25916
  if (isModern) {
25917
+ const tagName = isReminder ? "critical-reminders" : "directives";
25463
25918
  const rules = directives.map((d4) => ` <rule id="${d4.id}" priority="${d4.priority}">${d4.rule}</rule>`).join(`
25464
25919
  `);
25465
- return `<rules>
25920
+ if (isReminder) {
25921
+ return `<!-- Universal Pattern: High-priority directives repeated here for cross-LLM compatibility.
25922
+ Claude weights prompt beginning; GPT weights prompt end. Repetition ensures emphasis on both. -->
25923
+ <${tagName}>
25466
25924
  ${rules}
25467
- </rules>
25925
+ </${tagName}>
25926
+ `;
25927
+ }
25928
+ return `<${tagName}>
25929
+ ${rules}
25930
+ </${tagName}>
25931
+
25932
+ `;
25933
+ }
25934
+ let output = "";
25935
+ if (isReminder) {
25936
+ output += `<!-- Note: Critical directives repeated below for cross-model compatibility -->
25937
+ `;
25938
+ output += `## Critical Reminders
25468
25939
 
25469
25940
  `;
25470
25941
  }
25471
25942
  const highPriority = directives.filter((d4) => d4.priority === "high");
25472
25943
  const otherPriority = directives.filter((d4) => d4.priority !== "high");
25473
- let output = "";
25474
25944
  for (const d4 of highPriority) {
25475
25945
  output += `**IMPORTANT**: ${d4.rule}
25476
25946
 
25477
25947
  `;
25478
25948
  }
25479
- if (otherPriority.length > 0) {
25949
+ if (!isReminder && otherPriority.length > 0) {
25480
25950
  for (const d4 of otherPriority) {
25481
25951
  output += `- ${d4.rule}
25482
25952
  `;
@@ -25486,10 +25956,49 @@ ${rules}
25486
25956
  }
25487
25957
  return output;
25488
25958
  }
25959
+ function formatInstructionsForModel(instructions, family) {
25960
+ if (instructions.length === 0)
25961
+ return "";
25962
+ const isModern = family === "claude-modern" || family === "gpt-modern" || family === "unknown";
25963
+ if (isModern) {
25964
+ const rules = instructions.map((i2) => ` <guideline id="${i2.id}">${i2.rule}</guideline>`).join(`
25965
+ `);
25966
+ return `<instructions>
25967
+ ${rules}
25968
+ </instructions>
25969
+
25970
+ `;
25971
+ }
25972
+ let output = `## Guidelines
25973
+
25974
+ `;
25975
+ for (const i2 of instructions) {
25976
+ output += `- ${i2.rule}
25977
+ `;
25978
+ }
25979
+ output += `
25980
+ `;
25981
+ return output;
25982
+ }
25489
25983
  var compile_default = defineCommand({
25490
25984
  meta: {
25491
25985
  name: "compile",
25492
- description: "Compile an aspect's prompt for a specific model"
25986
+ description: `Compile an aspect's prompt for a specific model.
25987
+
25988
+ Formats directives and instructions optimally for the target model:
25989
+ - Modern models (Claude 4.x, GPT-4.1+): Clean XML tags
25990
+ - Legacy models (Claude 3, GPT-4): Markdown with emphasis
25991
+
25992
+ Cross-LLM Universal Pattern:
25993
+ High-priority directives are repeated at BOTH beginning and end.
25994
+ Claude weights prompt beginning; GPT weights prompt end.
25995
+ This ensures critical rules work across all models.
25996
+
25997
+ Examples:
25998
+ aspects compile alaric -m claude-haiku-4-5
25999
+ aspects compile alaric -m gpt-4o --mode campaign
26000
+ aspects compile ./my-aspect -m claude-4 -o prompt.txt
26001
+ aspects compile alaric -m claude-4 --verbose`
25493
26002
  },
25494
26003
  args: {
25495
26004
  name: {
@@ -25525,15 +26034,15 @@ var compile_default = defineCommand({
25525
26034
  try {
25526
26035
  const stats = await stat5(args.name);
25527
26036
  if (stats.isDirectory()) {
25528
- aspectPath = join8(args.name, "aspect.json");
26037
+ aspectPath = join9(args.name, "aspect.json");
25529
26038
  } else {
25530
26039
  aspectPath = args.name;
25531
26040
  }
25532
- aspectContent = await readFile7(aspectPath, "utf-8");
26041
+ aspectContent = await readFile6(aspectPath, "utf-8");
25533
26042
  } catch {
25534
26043
  try {
25535
- aspectPath = join8(ASPECTS_DIR, args.name, "aspect.json");
25536
- aspectContent = await readFile7(aspectPath, "utf-8");
26044
+ aspectPath = join9(ASPECTS_DIR, args.name, "aspect.json");
26045
+ aspectContent = await readFile6(aspectPath, "utf-8");
25537
26046
  } catch {
25538
26047
  M2.error(`Aspect not found: ${args.name}`);
25539
26048
  M2.info("Try: aspects list");
@@ -25563,7 +26072,8 @@ var compile_default = defineCommand({
25563
26072
  M2.info("");
25564
26073
  M2.info(`Model family: ${family}`);
25565
26074
  }
25566
- const directives = [];
26075
+ const directives = aspect.directives || [];
26076
+ const instructions = aspect.instructions || [];
25567
26077
  let modeInfo = null;
25568
26078
  if (args.mode && aspect.modes) {
25569
26079
  modeInfo = aspect.modes[args.mode] || null;
@@ -25588,6 +26098,9 @@ var compile_default = defineCommand({
25588
26098
  if (directives.length > 0) {
25589
26099
  compiled += formatDirectivesForModel(directives, family);
25590
26100
  }
26101
+ if (instructions.length > 0) {
26102
+ compiled += formatInstructionsForModel(instructions, family);
26103
+ }
25591
26104
  if (modeInfo?.critical) {
25592
26105
  if (family === "claude-modern" || family === "gpt-modern" || family === "unknown") {
25593
26106
  compiled += `<mode name="${args.mode}">
@@ -25603,6 +26116,13 @@ ${modeInfo.critical}
25603
26116
  }
25604
26117
  }
25605
26118
  compiled += aspect.prompt;
26119
+ const highPriorityDirectives = directives.filter((d4) => d4.priority === "high");
26120
+ if (highPriorityDirectives.length > 0) {
26121
+ compiled += `
26122
+
26123
+ `;
26124
+ compiled += formatDirectivesForModel(highPriorityDirectives, family, { isReminder: true });
26125
+ }
25606
26126
  if (aspect.voiceHints && args.verbose) {
25607
26127
  M2.info("");
25608
26128
  M2.info("Voice hints:");
@@ -25631,13 +26151,30 @@ ${modeInfo.critical}
25631
26151
  });
25632
26152
 
25633
26153
  // src/commands/publish.ts
25634
- import { readFile as readFile8, stat as stat6, readdir as readdir2 } from "node:fs/promises";
25635
- import { join as join9, dirname as dirname3 } from "node:path";
26154
+ import { readFile as readFile7, stat as stat6, readdir as readdir2 } from "node:fs/promises";
26155
+ import { join as join10, dirname as dirname3 } from "node:path";
25636
26156
  var MAX_ASPECT_SIZE = 51200;
25637
26157
  var publish_default = defineCommand({
25638
26158
  meta: {
25639
26159
  name: "publish",
25640
- description: "Publish an aspect to the registry"
26160
+ description: `Publish an aspect to the registry (requires login).
26161
+
26162
+ Publishing claims the aspect name under your account. You can then:
26163
+ - Publish new versions (bump version in aspect.json)
26164
+ - Update metadata (tagline, tags, category)
26165
+ - Build a publisher reputation
26166
+
26167
+ The publisher field in aspect.json must match your logged-in username.
26168
+
26169
+ Examples:
26170
+ aspects publish Interactive (scans for aspects)
26171
+ aspects publish ./my-aspect Publish specific aspect
26172
+ aspects publish --dry-run Validate without publishing
26173
+
26174
+ Don't want an account? Use 'aspects share' instead:
26175
+ - No login required
26176
+ - Content-addressed by Blake3 hash
26177
+ - Anyone can install via: aspects add blake3:<hash>`
25641
26178
  },
25642
26179
  args: {
25643
26180
  path: {
@@ -25666,7 +26203,21 @@ var publish_default = defineCommand({
25666
26203
  }
25667
26204
  let aspectPath;
25668
26205
  if (args.path) {
25669
- aspectPath = args.path;
26206
+ const inputPath = args.path;
26207
+ try {
26208
+ await stat6(inputPath);
26209
+ aspectPath = inputPath;
26210
+ } catch {
26211
+ const projectRoot = await findProjectRoot() || undefined;
26212
+ const installed = await findInstalledAspect(inputPath, projectRoot);
26213
+ if (installed.length > 0) {
26214
+ const match = installed.find((i2) => i2.scope === "project") || installed[0];
26215
+ aspectPath = getAspectPath(inputPath, match.scope, projectRoot);
26216
+ M2.info(`Found installed: ${c3.aspect(inputPath)} ${c3.dim(`[${match.scope}]`)}`);
26217
+ } else {
26218
+ aspectPath = inputPath;
26219
+ }
26220
+ }
25670
26221
  } else {
25671
26222
  const spinner = Y3();
25672
26223
  spinner.start("Scanning for aspects...");
@@ -25716,13 +26267,37 @@ var publish_default = defineCommand({
25716
26267
  M2.error(`Aspect too large: ${sizeBytes} bytes (${MAX_ASPECT_SIZE} byte limit)`);
25717
26268
  process.exit(1);
25718
26269
  }
25719
- if (!dryRun) {
25720
- const auth = await getAuth();
25721
- if (validation.aspect.publisher && auth && validation.aspect.publisher !== auth.username) {
25722
- spinner2.stop("Validation failed");
25723
- M2.error(`Publisher mismatch: aspect.json has "${validation.aspect.publisher}" but you are "@${auth.username}"`);
25724
- M2.info("Update the publisher field in aspect.json or log in with the correct account.");
25725
- process.exit(1);
26270
+ const auth = await getAuth();
26271
+ if (!dryRun && auth) {
26272
+ const defaultHandle = await getDefaultHandle();
26273
+ const handles = await getHandles();
26274
+ if (validation.aspect.publisher) {
26275
+ const hasPermission = await hasHandlePermission(validation.aspect.publisher);
26276
+ if (!hasPermission) {
26277
+ spinner2.stop("Validation failed");
26278
+ console.log();
26279
+ M2.error(`You don't have permission to publish under @${c3.bold(validation.aspect.publisher)}`);
26280
+ console.log();
26281
+ console.log(c3.muted(" Your handles:"));
26282
+ for (const h4 of handles) {
26283
+ const isDefault = h4.name === defaultHandle;
26284
+ console.log(` @${h4.name}${isDefault ? c3.dim(" (default)") : ""}`);
26285
+ }
26286
+ console.log();
26287
+ console.log(c3.muted(" Either:"));
26288
+ console.log(` 1. Change "publisher" in aspect.json to one of your handles`);
26289
+ console.log(` 2. Get added as a member to @${validation.aspect.publisher} (via web UI)`);
26290
+ console.log(` 3. Use ${c3.cmd("aspects share")} for anonymous publishing`);
26291
+ console.log();
26292
+ process.exit(1);
26293
+ }
26294
+ } else {
26295
+ if (!defaultHandle) {
26296
+ spinner2.stop("Validation failed");
26297
+ M2.error('No default handle set. Run "aspects handle claim <name>" first.');
26298
+ process.exit(1);
26299
+ }
26300
+ validation.aspect.publisher = defaultHandle;
25726
26301
  }
25727
26302
  }
25728
26303
  spinner2.stop("Validation passed");
@@ -25753,6 +26328,10 @@ var publish_default = defineCommand({
25753
26328
  spinner3.start(`Publishing ${validation.aspect.name}@${validation.aspect.version}...`);
25754
26329
  try {
25755
26330
  const parsed = JSON.parse(validation.content);
26331
+ const defaultHandle = await getDefaultHandle();
26332
+ if (defaultHandle && !parsed.publisher) {
26333
+ parsed.publisher = defaultHandle;
26334
+ }
25756
26335
  const result = await publishAspect(parsed);
25757
26336
  spinner3.stop("Published");
25758
26337
  console.log();
@@ -25773,6 +26352,10 @@ var publish_default = defineCommand({
25773
26352
  } else if (err.errorCode === "unauthorized") {
25774
26353
  M2.info("");
25775
26354
  M2.info('Run "aspects login" to authenticate');
26355
+ } else if (err.errorCode === "no_permission") {
26356
+ M2.info("");
26357
+ M2.info("You don't have permission to publish under this handle");
26358
+ M2.info('Run "aspects handle list" to see your handles');
25776
26359
  }
25777
26360
  } else {
25778
26361
  M2.error(`Publish failed: ${err.message}`);
@@ -25786,13 +26369,13 @@ async function loadAspectFromPath2(inputPath) {
25786
26369
  try {
25787
26370
  const stats = await stat6(inputPath);
25788
26371
  if (stats.isDirectory()) {
25789
- aspectPath = join9(inputPath, "aspect.json");
26372
+ aspectPath = join10(inputPath, "aspect.json");
25790
26373
  }
25791
26374
  } catch {
25792
26375
  return null;
25793
26376
  }
25794
26377
  try {
25795
- const content = await readFile8(aspectPath, "utf-8");
26378
+ const content = await readFile7(aspectPath, "utf-8");
25796
26379
  const aspect = JSON.parse(content);
25797
26380
  return {
25798
26381
  path: dirname3(aspectPath),
@@ -25818,18 +26401,33 @@ async function findLocalAspects() {
25818
26401
  const entries = await readdir2(process.cwd(), { withFileTypes: true });
25819
26402
  for (const entry of entries) {
25820
26403
  if (entry.isDirectory() && !entry.name.startsWith(".")) {
25821
- const subAspect = await loadAspectFromPath2(join9(process.cwd(), entry.name));
26404
+ const subAspect = await loadAspectFromPath2(join10(process.cwd(), entry.name));
25822
26405
  if (subAspect && !aspects.find((a2) => a2.path === subAspect.path)) {
25823
26406
  aspects.push(subAspect);
25824
26407
  }
25825
26408
  }
25826
26409
  }
25827
26410
  } catch {}
26411
+ const projectRoot = await findProjectRoot();
26412
+ if (projectRoot) {
26413
+ const projectAspectsDir = join10(projectRoot, ".aspects", "aspects");
26414
+ try {
26415
+ const entries = await readdir2(projectAspectsDir, { withFileTypes: true });
26416
+ for (const entry of entries) {
26417
+ if (entry.isDirectory()) {
26418
+ const projectAspect = await loadAspectFromPath2(join10(projectAspectsDir, entry.name));
26419
+ if (projectAspect && !aspects.find((a2) => a2.path === projectAspect.path)) {
26420
+ aspects.push(projectAspect);
26421
+ }
26422
+ }
26423
+ }
26424
+ } catch {}
26425
+ }
25828
26426
  try {
25829
26427
  const entries = await readdir2(ASPECTS_DIR, { withFileTypes: true });
25830
26428
  for (const entry of entries) {
25831
26429
  if (entry.isDirectory()) {
25832
- const installedAspect = await loadAspectFromPath2(join9(ASPECTS_DIR, entry.name));
26430
+ const installedAspect = await loadAspectFromPath2(join10(ASPECTS_DIR, entry.name));
25833
26431
  if (installedAspect && !aspects.find((a2) => a2.path === installedAspect.path)) {
25834
26432
  aspects.push(installedAspect);
25835
26433
  }
@@ -25843,14 +26441,14 @@ async function validateAspect(aspectPath) {
25843
26441
  try {
25844
26442
  const stats = await stat6(aspectPath);
25845
26443
  if (stats.isDirectory()) {
25846
- filePath = join9(aspectPath, "aspect.json");
26444
+ filePath = join10(aspectPath, "aspect.json");
25847
26445
  }
25848
26446
  } catch {
25849
26447
  return { valid: false, errors: [`Path not found: ${aspectPath}`] };
25850
26448
  }
25851
26449
  let content;
25852
26450
  try {
25853
- content = await readFile8(filePath, "utf-8");
26451
+ content = await readFile7(filePath, "utf-8");
25854
26452
  } catch {
25855
26453
  return { valid: false, errors: [`Cannot read file: ${filePath}`] };
25856
26454
  }
@@ -25891,8 +26489,8 @@ async function validateAspect(aspectPath) {
25891
26489
  }
25892
26490
 
25893
26491
  // src/commands/edit.ts
25894
- import { readFile as readFile9, writeFile as writeFile6 } from "node:fs/promises";
25895
- import { join as join10 } from "node:path";
26492
+ import { readFile as readFile8, writeFile as writeFile6 } from "node:fs/promises";
26493
+ import { join as join11 } from "node:path";
25896
26494
  var CATEGORIES = [
25897
26495
  "assistant",
25898
26496
  "roleplay",
@@ -25909,7 +26507,7 @@ async function listLocalAspects() {
25909
26507
  const results = [];
25910
26508
  for (const [name, info] of Object.entries(config2.installed)) {
25911
26509
  const aspectPath = info.path || getAspectPath(name);
25912
- const parseResult = await parseAspectFile(join10(aspectPath, "aspect.json"));
26510
+ const parseResult = await parseAspectFile(join11(aspectPath, "aspect.json"));
25913
26511
  if (parseResult.success) {
25914
26512
  results.push({ name, path: aspectPath, aspect: parseResult.aspect });
25915
26513
  }
@@ -26065,8 +26663,8 @@ var edit_default = defineCommand({
26065
26663
  xe("Cancelled");
26066
26664
  return;
26067
26665
  }
26068
- const aspectPath = join10(selectedAspect.path, "aspect.json");
26069
- const content = await readFile9(aspectPath, "utf-8");
26666
+ const aspectPath = join11(selectedAspect.path, "aspect.json");
26667
+ const content = await readFile8(aspectPath, "utf-8");
26070
26668
  let updatedContent = content;
26071
26669
  if (changes.includes("displayName")) {
26072
26670
  updatedContent = updatedContent.replace(/displayName:.*$/m, `displayName: "${updatedAspect.displayName}"`);
@@ -26103,8 +26701,8 @@ ${updatedAspect.tags.map((t4) => ` - ${t4}`).join(`
26103
26701
  });
26104
26702
 
26105
26703
  // src/commands/bundle.ts
26106
- import { readFile as readFile10, writeFile as writeFile7 } from "node:fs/promises";
26107
- import { join as join11 } from "node:path";
26704
+ import { readFile as readFile9, writeFile as writeFile7 } from "node:fs/promises";
26705
+ import { join as join12 } from "node:path";
26108
26706
  var import_picocolors4 = __toESM(require_picocolors(), 1);
26109
26707
  function parseQuery2(query) {
26110
26708
  const filters = [];
@@ -26184,8 +26782,8 @@ async function loadLocalAspects() {
26184
26782
  const aspects = [];
26185
26783
  for (const name of Object.keys(config2.installed || {})) {
26186
26784
  try {
26187
- const aspectPath = join11(getAspectPath(name), "aspect.json");
26188
- const content = await readFile10(aspectPath, "utf-8");
26785
+ const aspectPath = join12(getAspectPath(name), "aspect.json");
26786
+ const content = await readFile9(aspectPath, "utf-8");
26189
26787
  aspects.push(JSON.parse(content));
26190
26788
  } catch {}
26191
26789
  }
@@ -26268,8 +26866,8 @@ var bundle_default = defineCommand({
26268
26866
  }
26269
26867
  }
26270
26868
  } else {
26271
- const aspectPath = join11(getAspectPath(name), "aspect.json");
26272
- const content = await readFile10(aspectPath, "utf-8");
26869
+ const aspectPath = join12(getAspectPath(name), "aspect.json");
26870
+ const content = await readFile9(aspectPath, "utf-8");
26273
26871
  aspectsToBundle.push(JSON.parse(content));
26274
26872
  }
26275
26873
  } catch (error48) {
@@ -26318,8 +26916,8 @@ var bundle_default = defineCommand({
26318
26916
  }
26319
26917
  }
26320
26918
  } else {
26321
- const aspectPath = join11(getAspectPath(aspectName), "aspect.json");
26322
- const content = await readFile10(aspectPath, "utf-8");
26919
+ const aspectPath = join12(getAspectPath(aspectName), "aspect.json");
26920
+ const content = await readFile9(aspectPath, "utf-8");
26323
26921
  const aspect = JSON.parse(content);
26324
26922
  if (!aspectsToBundle.some((a2) => a2.name === aspect.name)) {
26325
26923
  aspectsToBundle.push(aspect);
@@ -26364,8 +26962,8 @@ var bundle_default = defineCommand({
26364
26962
  });
26365
26963
  async function loadSet2(name) {
26366
26964
  try {
26367
- const setPath = join11(getSetsDir(), name, "set.json");
26368
- const content = await readFile10(setPath, "utf-8");
26965
+ const setPath = join12(getSetsDir(), name, "set.json");
26966
+ const content = await readFile9(setPath, "utf-8");
26369
26967
  return JSON.parse(content);
26370
26968
  } catch {
26371
26969
  return null;
@@ -26373,16 +26971,39 @@ async function loadSet2(name) {
26373
26971
  }
26374
26972
 
26375
26973
  // src/commands/login.ts
26974
+ import { exec } from "node:child_process";
26975
+ import * as readline from "node:readline/promises";
26976
+ import { stdin as input, stdout as output } from "node:process";
26376
26977
  var login_default = defineCommand({
26377
26978
  meta: {
26378
26979
  name: "login",
26379
- description: "Authenticate with the aspects registry"
26980
+ description: `Authenticate with the aspects registry.
26981
+
26982
+ Uses device authorization flow (like GitHub CLI):
26983
+ 1. CLI requests a device code
26984
+ 2. Browser opens to verification URL
26985
+ 3. Enter the code and authorize
26986
+ 4. CLI receives and stores access token
26987
+
26988
+ Benefits of logging in:
26989
+ - Name ownership: Claim aspect names under your publisher ID
26990
+ - Versioning: Publish updates to your aspects
26991
+ - Edit metadata: Update tagline, tags, category
26992
+
26993
+ Credentials stored in ~/.aspects/config.json
26994
+
26995
+ Don't want an account? Use 'aspects share' to publish anonymously.`
26380
26996
  },
26381
26997
  async run() {
26382
26998
  const auth = await getAuth();
26383
26999
  if (auth && await isLoggedIn()) {
27000
+ const defaultHandle = await getDefaultHandle();
26384
27001
  console.log();
26385
- console.log(`${icons2.info} Already logged in as ${c3.bold(`@${auth.username}`)}`);
27002
+ if (defaultHandle) {
27003
+ console.log(`${icons2.info} Already logged in as ${c3.bold(`@${defaultHandle}`)}`);
27004
+ } else {
27005
+ console.log(`${icons2.info} Already logged in`);
27006
+ }
26386
27007
  console.log(c3.muted(' Run "aspects logout" to sign out first.'));
26387
27008
  console.log();
26388
27009
  return;
@@ -26393,7 +27014,14 @@ var login_default = defineCommand({
26393
27014
  try {
26394
27015
  deviceCode = await initiateDeviceAuth();
26395
27016
  } catch (err) {
26396
- log.error(`Failed to initiate login: ${err.message}`);
27017
+ const error48 = err;
27018
+ log.error(`Failed to initiate login: ${error48.message}`);
27019
+ if (error48.statusCode) {
27020
+ console.log(c3.muted(` Status: ${error48.statusCode}`));
27021
+ }
27022
+ if (error48.errorCode) {
27023
+ console.log(c3.muted(` Code: ${error48.errorCode}`));
27024
+ }
26397
27025
  process.exit(1);
26398
27026
  }
26399
27027
  console.log();
@@ -26403,7 +27031,6 @@ var login_default = defineCommand({
26403
27031
  console.log(` Code: ${c3.bold(deviceCode.user_code)}`);
26404
27032
  console.log();
26405
27033
  try {
26406
- const { exec } = await import("node:child_process");
26407
27034
  const platform2 = process.platform;
26408
27035
  const openCmd = platform2 === "darwin" ? "open" : platform2 === "win32" ? "start" : "xdg-open";
26409
27036
  exec(`${openCmd} "${deviceCode.verification_uri_complete}"`);
@@ -26419,15 +27046,44 @@ var login_default = defineCommand({
26419
27046
  if (result.ok && result.access_token) {
26420
27047
  const expiresIn = result.expires_in ?? 3600;
26421
27048
  const expiresAtDate = new Date(Date.now() + expiresIn * 1000).toISOString();
26422
- const username = extractUsernameFromToken(result.access_token) ?? "user";
27049
+ const account = result.account;
27050
+ if (!account) {
27051
+ log.error("API did not return account info. Please update the registry.");
27052
+ process.exit(1);
27053
+ }
27054
+ if (account.needs_handle) {
27055
+ console.log(`${icons2.success} Authenticated!`);
27056
+ console.log();
27057
+ const suggested = extractUsernameFromToken(result.access_token);
27058
+ const handle = await promptForHandle(suggested, result.access_token);
27059
+ await setAuthTokens({
27060
+ accessToken: result.access_token,
27061
+ refreshToken: result.refresh_token,
27062
+ expiresAt: expiresAtDate,
27063
+ accountId: account.id,
27064
+ handles: [{ name: handle, role: "owner", default: true }],
27065
+ defaultHandle: handle
27066
+ });
27067
+ console.log();
27068
+ console.log(`${icons2.success} Logged in as ${c3.bold(`@${handle}`)}`);
27069
+ console.log(c3.muted(" Credentials stored in ~/.aspects/config.json"));
27070
+ console.log();
27071
+ return;
27072
+ }
27073
+ const defaultHandle = account.handles.find((h4) => h4.default)?.name ?? account.handles[0]?.name ?? "";
26423
27074
  await setAuthTokens({
26424
27075
  accessToken: result.access_token,
26425
27076
  refreshToken: result.refresh_token,
26426
27077
  expiresAt: expiresAtDate,
26427
- username
27078
+ accountId: account.id,
27079
+ handles: account.handles,
27080
+ defaultHandle
26428
27081
  });
26429
- console.log(`${icons2.success} Authorized as ${c3.bold(`@${username}`)}`);
26430
- console.log(c3.muted(" Access token stored in ~/.aspects/config.json"));
27082
+ console.log(`${icons2.success} Logged in as ${c3.bold(`@${defaultHandle}`)}`);
27083
+ if (account.handles.length > 1) {
27084
+ console.log(c3.muted(` Also: ${account.handles.filter((h4) => h4.name !== defaultHandle).map((h4) => `@${h4.name}`).join(", ")}`));
27085
+ }
27086
+ console.log(c3.muted(" Credentials stored in ~/.aspects/config.json"));
26431
27087
  console.log();
26432
27088
  return;
26433
27089
  }
@@ -26465,11 +27121,75 @@ function extractUsernameFromToken(token) {
26465
27121
  if (parts.length !== 3)
26466
27122
  return null;
26467
27123
  const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
26468
- return payload.username ?? payload.sub ?? payload.preferred_username ?? null;
27124
+ const raw = payload.preferred_username ?? payload.email ?? payload.username ?? payload.name ?? null;
27125
+ if (!raw)
27126
+ return null;
27127
+ let clean2 = raw.split("@")[0] ?? raw;
27128
+ clean2 = clean2.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/--+/g, "-").replace(/^-|-$/g, "");
27129
+ if (clean2.length < 2 || clean2.length > 39)
27130
+ return null;
27131
+ return clean2;
26469
27132
  } catch {
26470
27133
  return null;
26471
27134
  }
26472
27135
  }
27136
+ async function promptForHandle(suggested, accessToken) {
27137
+ const rl2 = readline.createInterface({ input, output });
27138
+ const HANDLE_REGEX = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
27139
+ console.log(` ${c3.bold("Claim your handle")}`);
27140
+ console.log(` This will be your publisher identity (e.g., @${suggested ?? "yourname"})`);
27141
+ console.log();
27142
+ while (true) {
27143
+ const prompt2 = suggested ? ` Handle [${suggested}]: ` : " Handle: ";
27144
+ let answer = await rl2.question(prompt2);
27145
+ answer = answer.trim().toLowerCase().replace(/^@/, "");
27146
+ if (!answer && suggested) {
27147
+ answer = suggested;
27148
+ }
27149
+ if (!answer) {
27150
+ console.log(c3.warn(" Please enter a handle"));
27151
+ continue;
27152
+ }
27153
+ if (answer.length < 2) {
27154
+ console.log(c3.warn(" Handle must be at least 2 characters"));
27155
+ continue;
27156
+ }
27157
+ if (answer.length > 39) {
27158
+ console.log(c3.warn(" Handle must be at most 39 characters"));
27159
+ continue;
27160
+ }
27161
+ if (!HANDLE_REGEX.test(answer)) {
27162
+ console.log(c3.warn(" Handle must be lowercase alphanumeric with hyphens"));
27163
+ continue;
27164
+ }
27165
+ if (answer.includes("--")) {
27166
+ console.log(c3.warn(" Handle cannot contain consecutive hyphens"));
27167
+ continue;
27168
+ }
27169
+ try {
27170
+ console.log(` ${icons2.working} Claiming @${answer}...`);
27171
+ await claimHandle(answer);
27172
+ rl2.close();
27173
+ return answer;
27174
+ } catch (err) {
27175
+ if (err instanceof ApiClientError) {
27176
+ switch (err.errorCode) {
27177
+ case "handle_taken":
27178
+ console.log(c3.warn(` @${answer} is already taken. Try another.`));
27179
+ break;
27180
+ case "handle_reserved":
27181
+ console.log(c3.warn(` @${answer} is reserved. Try another.`));
27182
+ break;
27183
+ default:
27184
+ console.log(c3.warn(` ${err.message}`));
27185
+ }
27186
+ continue;
27187
+ }
27188
+ rl2.close();
27189
+ throw err;
27190
+ }
27191
+ }
27192
+ }
26473
27193
 
26474
27194
  // src/commands/logout.ts
26475
27195
  var logout_default = defineCommand({
@@ -26485,10 +27205,10 @@ var logout_default = defineCommand({
26485
27205
  console.log();
26486
27206
  return;
26487
27207
  }
26488
- const username = auth.username;
27208
+ const handle = auth.defaultHandle;
26489
27209
  await clearAuth();
26490
27210
  console.log();
26491
- console.log(`${icons2.success} Logged out${username ? ` from @${username}` : ""}`);
27211
+ console.log(`${icons2.success} Logged out${handle ? ` from @${handle}` : ""}`);
26492
27212
  console.log(c3.muted(" Auth tokens removed from ~/.aspects/config.json"));
26493
27213
  console.log();
26494
27214
  }
@@ -26496,12 +27216,29 @@ var logout_default = defineCommand({
26496
27216
 
26497
27217
  // src/commands/share.ts
26498
27218
  import { stat as stat7 } from "node:fs/promises";
26499
- import { join as join12 } from "node:path";
27219
+ import { join as join13 } from "node:path";
26500
27220
  var MAX_ASPECT_SIZE2 = 51200;
26501
27221
  var share_default = defineCommand({
26502
27222
  meta: {
26503
27223
  name: "share",
26504
- description: "Share an aspect anonymously via content hash (no account required)"
27224
+ description: `Share an aspect anonymously via content hash (no account required).
27225
+
27226
+ How it works:
27227
+ 1. Computes Blake3 hash of your aspect content
27228
+ 2. Uploads to registry (content-addressed storage)
27229
+ 3. Anyone can install via: aspects add blake3:<hash>
27230
+
27231
+ No account needed! Perfect for:
27232
+ - Quick one-off sharing
27233
+ - Testing before claiming a name
27234
+ - Anonymous contributions
27235
+
27236
+ Examples:
27237
+ aspects share my-aspect Share an installed aspect
27238
+ aspects share ./path/to/aspect Share from local path
27239
+ aspects share my-aspect --dry-run Preview hash without uploading
27240
+
27241
+ Want to claim a name instead? Use 'aspects publish' (requires login).`
26505
27242
  },
26506
27243
  args: {
26507
27244
  target: {
@@ -26543,8 +27280,8 @@ var share_default = defineCommand({
26543
27280
  try {
26544
27281
  const stats = await stat7(target);
26545
27282
  if (stats.isDirectory()) {
26546
- const jsonPath = join12(target, "aspect.json");
26547
- const yamlPath = join12(target, "aspect.yaml");
27283
+ const jsonPath = join13(target, "aspect.json");
27284
+ const yamlPath = join13(target, "aspect.yaml");
26548
27285
  try {
26549
27286
  await stat7(jsonPath);
26550
27287
  filePath = jsonPath;
@@ -26563,13 +27300,14 @@ var share_default = defineCommand({
26563
27300
  }
26564
27301
  aspect = result.aspect;
26565
27302
  } else {
26566
- const loaded = await loadInstalledAspect(target);
26567
- if (!loaded) {
27303
+ const found = await findAndLoadAspect(target);
27304
+ if (!found) {
26568
27305
  M2.error(`Aspect "${target}" is not installed or cannot be read`);
26569
27306
  M2.info("To share from a path, use: aspects share ./path/to/aspect.json");
26570
27307
  process.exit(1);
26571
27308
  }
26572
- aspect = loaded;
27309
+ M2.info(`Found: ${c3.aspect(target)} ${c3.dim(`[${found.scope}]`)}`);
27310
+ aspect = found.aspect;
26573
27311
  }
26574
27312
  const content = canonicalizeAspect(aspect);
26575
27313
  const sizeBytes = Buffer.byteLength(content);
@@ -26586,7 +27324,7 @@ var share_default = defineCommand({
26586
27324
  console.log(` ${c3.label("Hash")} ${hash2}`);
26587
27325
  console.log();
26588
27326
  if (dryRun) {
26589
- M2.info("Dry run not uploading");
27327
+ M2.info("Dry run - not uploading");
26590
27328
  console.log();
26591
27329
  console.log(` ${c3.label("Install")} aspects add hash:${hash2}`);
26592
27330
  console.log();
@@ -26608,13 +27346,16 @@ var share_default = defineCommand({
26608
27346
  console.log();
26609
27347
  console.log(`${icons2.success} ${c3.bold("Shared successfully!")}`);
26610
27348
  console.log();
26611
- console.log(` ${c3.label("Hash")} ${response.blake3}`);
26612
- console.log(` ${c3.label("Install")} ${c3.highlight(`aspects add blake3:${response.blake3}`)}`);
27349
+ console.log(` ${c3.label("Name")} ${c3.highlight(response.name)}`);
27350
+ console.log(` ${c3.label("Install")} ${c3.highlight(`aspects add ${response.name}`)}`);
27351
+ console.log(` ${c3.label("URL")} ${response.url}`);
27352
+ console.log();
27353
+ console.log(` ${c3.muted("Or by hash:")} aspects add blake3:${response.blake3}`);
26613
27354
  if (response.existing) {
26614
27355
  console.log(` ${c3.muted("(Already existed on registry)")}`);
26615
27356
  }
26616
27357
  console.log();
26617
- Se("Share this hash with anyone to let them install your aspect!");
27358
+ Se("Share this name or URL with anyone to let them install your aspect!");
26618
27359
  } catch (err) {
26619
27360
  spinner.stop("Upload failed");
26620
27361
  if (err instanceof ApiClientError) {
@@ -26699,7 +27440,501 @@ var unpublish_default = defineCommand({
26699
27440
  }
26700
27441
  });
26701
27442
 
27443
+ // src/commands/config.ts
27444
+ import { homedir as homedir2 } from "node:os";
27445
+ var DEFAULT_REGISTRY_URL = "https://aspects.sh/api/v1";
27446
+ function tildify(path) {
27447
+ const home = homedir2();
27448
+ return path.startsWith(home) ? path.replace(home, "~") : path;
27449
+ }
27450
+ var CONFIG_KEYS = {
27451
+ "registry.url": {
27452
+ path: ["settings", "registryUrl"],
27453
+ description: "API registry URL",
27454
+ default: DEFAULT_REGISTRY_URL
27455
+ }
27456
+ };
27457
+ function getNestedValue(obj, path) {
27458
+ let current = obj;
27459
+ for (const key of path) {
27460
+ if (current && typeof current === "object" && key in current) {
27461
+ current = current[key];
27462
+ } else {
27463
+ return;
27464
+ }
27465
+ }
27466
+ return current;
27467
+ }
27468
+ function setNestedValue(obj, path, value) {
27469
+ let current = obj;
27470
+ for (let i2 = 0;i2 < path.length - 1; i2++) {
27471
+ const key = path[i2];
27472
+ if (!(key in current) || typeof current[key] !== "object") {
27473
+ current[key] = {};
27474
+ }
27475
+ current = current[key];
27476
+ }
27477
+ const lastKey = path[path.length - 1];
27478
+ if (value === undefined) {
27479
+ delete current[lastKey];
27480
+ } else {
27481
+ current[lastKey] = value;
27482
+ }
27483
+ }
27484
+ var listCommand2 = defineCommand({
27485
+ meta: {
27486
+ name: "list",
27487
+ description: "Show all configuration values"
27488
+ },
27489
+ async run() {
27490
+ const config2 = await readConfig();
27491
+ const registryUrl = await getRegistryUrl();
27492
+ const auth = await getAuth();
27493
+ console.log();
27494
+ console.log(`${icons2.info} Configuration`);
27495
+ console.log();
27496
+ console.log(` ${c3.label("Config file")} ${tildify(CONFIG_PATH)}`);
27497
+ console.log();
27498
+ console.log(c3.bold(" Registry"));
27499
+ const envUrl = process.env.ASPECTS_REGISTRY_URL;
27500
+ if (envUrl) {
27501
+ console.log(` ${c3.label("url")} ${registryUrl} ${c3.dim("(from ASPECTS_REGISTRY_URL)")}`);
27502
+ } else if (config2.settings.registryUrl) {
27503
+ console.log(` ${c3.label("url")} ${registryUrl}`);
27504
+ } else {
27505
+ console.log(` ${c3.label("url")} ${registryUrl} ${c3.dim("(default)")}`);
27506
+ }
27507
+ console.log();
27508
+ console.log(c3.bold(" Auth"));
27509
+ if (auth) {
27510
+ console.log(` ${c3.label("logged in as")} @${auth.defaultHandle}`);
27511
+ const expiresAt = new Date(auth.expiresAt);
27512
+ const isExpired = expiresAt < new Date;
27513
+ if (isExpired) {
27514
+ console.log(` ${c3.label("token")} ${c3.warn("expired")} ${c3.dim(`(${expiresAt.toLocaleString()})`)}`);
27515
+ } else {
27516
+ console.log(` ${c3.label("token")} valid until ${c3.dim(expiresAt.toLocaleString())}`);
27517
+ }
27518
+ } else {
27519
+ console.log(` ${c3.muted("Not logged in")}`);
27520
+ }
27521
+ const installedCount = Object.keys(config2.installed).length;
27522
+ console.log();
27523
+ console.log(c3.bold(" Library"));
27524
+ console.log(` ${c3.label("aspects home")} ${tildify(ASPECTS_HOME)}`);
27525
+ console.log(` ${c3.label("installed")} ${installedCount} aspect${installedCount === 1 ? "" : "s"}`);
27526
+ console.log();
27527
+ }
27528
+ });
27529
+ var getCommand = defineCommand({
27530
+ meta: {
27531
+ name: "get",
27532
+ description: "Get a configuration value"
27533
+ },
27534
+ args: {
27535
+ key: {
27536
+ type: "positional",
27537
+ description: "Config key (e.g., registry.url)",
27538
+ required: true
27539
+ }
27540
+ },
27541
+ async run({ args }) {
27542
+ const key = args.key;
27543
+ const config2 = await readConfig();
27544
+ const knownKey = CONFIG_KEYS[key];
27545
+ if (knownKey) {
27546
+ const value = getNestedValue(config2, knownKey.path);
27547
+ const effectiveValue = value ?? knownKey.default;
27548
+ if (key === "registry.url" && process.env.ASPECTS_REGISTRY_URL) {
27549
+ console.log(process.env.ASPECTS_REGISTRY_URL);
27550
+ return;
27551
+ }
27552
+ console.log(effectiveValue);
27553
+ } else {
27554
+ const path = key.split(".");
27555
+ const value = getNestedValue(config2, path);
27556
+ if (value !== undefined) {
27557
+ console.log(typeof value === "object" ? JSON.stringify(value, null, 2) : value);
27558
+ } else {
27559
+ console.error(`Unknown config key: ${key}`);
27560
+ process.exit(1);
27561
+ }
27562
+ }
27563
+ }
27564
+ });
27565
+ var setCommand = defineCommand({
27566
+ meta: {
27567
+ name: "set",
27568
+ description: "Set a configuration value"
27569
+ },
27570
+ args: {
27571
+ key: {
27572
+ type: "positional",
27573
+ description: "Config key (e.g., registry.url)",
27574
+ required: true
27575
+ },
27576
+ value: {
27577
+ type: "positional",
27578
+ description: "Value to set",
27579
+ required: true
27580
+ }
27581
+ },
27582
+ async run({ args }) {
27583
+ const key = args.key;
27584
+ const value = args.value;
27585
+ const config2 = await readConfig();
27586
+ const knownKey = CONFIG_KEYS[key];
27587
+ if (knownKey) {
27588
+ setNestedValue(config2, knownKey.path, value);
27589
+ await writeConfig(config2);
27590
+ console.log(`${icons2.success} Set ${key} = ${value}`);
27591
+ } else {
27592
+ const path = key.split(".");
27593
+ setNestedValue(config2, path, value);
27594
+ await writeConfig(config2);
27595
+ console.log(`${icons2.success} Set ${key} = ${value}`);
27596
+ }
27597
+ }
27598
+ });
27599
+ var unsetCommand = defineCommand({
27600
+ meta: {
27601
+ name: "unset",
27602
+ description: "Remove a configuration value (revert to default)"
27603
+ },
27604
+ args: {
27605
+ key: {
27606
+ type: "positional",
27607
+ description: "Config key to remove",
27608
+ required: true
27609
+ }
27610
+ },
27611
+ async run({ args }) {
27612
+ const key = args.key;
27613
+ const config2 = await readConfig();
27614
+ const knownKey = CONFIG_KEYS[key];
27615
+ const path = knownKey ? knownKey.path : key.split(".");
27616
+ const currentValue = getNestedValue(config2, path);
27617
+ if (currentValue === undefined) {
27618
+ console.log(`${icons2.info} ${key} is not set`);
27619
+ return;
27620
+ }
27621
+ setNestedValue(config2, path, undefined);
27622
+ await writeConfig(config2);
27623
+ if (knownKey) {
27624
+ console.log(`${icons2.success} Unset ${key} (will use default: ${knownKey.default})`);
27625
+ } else {
27626
+ console.log(`${icons2.success} Unset ${key}`);
27627
+ }
27628
+ }
27629
+ });
27630
+ var pathCommand = defineCommand({
27631
+ meta: {
27632
+ name: "path",
27633
+ description: "Show path to config file"
27634
+ },
27635
+ async run() {
27636
+ console.log(CONFIG_PATH);
27637
+ }
27638
+ });
27639
+ var config_default = defineCommand({
27640
+ meta: {
27641
+ name: "config",
27642
+ description: "View and modify configuration"
27643
+ },
27644
+ subCommands: {
27645
+ list: listCommand2,
27646
+ get: getCommand,
27647
+ set: setCommand,
27648
+ unset: unsetCommand,
27649
+ path: pathCommand
27650
+ }
27651
+ });
27652
+
27653
+ // src/commands/handle.ts
27654
+ var HANDLE_REGEX = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
27655
+ var MIN_LENGTH = 2;
27656
+ var MAX_LENGTH = 39;
27657
+ function validateHandleFormat(name) {
27658
+ if (name.length < MIN_LENGTH) {
27659
+ return `Handle must be at least ${MIN_LENGTH} characters`;
27660
+ }
27661
+ if (name.length > MAX_LENGTH) {
27662
+ return `Handle must be at most ${MAX_LENGTH} characters`;
27663
+ }
27664
+ if (!HANDLE_REGEX.test(name)) {
27665
+ return "Handle must be lowercase alphanumeric with hyphens, cannot start or end with hyphen";
27666
+ }
27667
+ if (name.includes("--")) {
27668
+ return "Handle cannot contain consecutive hyphens";
27669
+ }
27670
+ return null;
27671
+ }
27672
+ var claimCommand = defineCommand({
27673
+ meta: {
27674
+ name: "claim",
27675
+ description: "Claim a new handle"
27676
+ },
27677
+ args: {
27678
+ name: {
27679
+ type: "positional",
27680
+ description: "Handle name to claim (e.g., myhandle)",
27681
+ required: true
27682
+ },
27683
+ "display-name": {
27684
+ type: "string",
27685
+ description: "Display name with preferred casing"
27686
+ }
27687
+ },
27688
+ async run({ args }) {
27689
+ const auth = await getAuth();
27690
+ if (!auth) {
27691
+ console.error(`${icons2.error} Not logged in. Run ${c3.cmd("aspects login")} first.`);
27692
+ process.exit(1);
27693
+ }
27694
+ const name = args.name.toLowerCase();
27695
+ const displayName = args["display-name"];
27696
+ const formatError2 = validateHandleFormat(name);
27697
+ if (formatError2) {
27698
+ console.error(`${icons2.error} ${formatError2}`);
27699
+ process.exit(1);
27700
+ }
27701
+ try {
27702
+ console.log(`${icons2.working} Claiming @${name}...`);
27703
+ const result = await claimHandle(name, displayName);
27704
+ const account = await getAccount();
27705
+ await updateHandles(account.handles);
27706
+ console.log(`${icons2.success} Claimed @${result.name}`);
27707
+ console.log();
27708
+ console.log(` You can now publish aspects under this handle.`);
27709
+ console.log(` Use ${c3.cmd(`aspects handle default ${name}`)} to make it your default.`);
27710
+ } catch (err) {
27711
+ if (err instanceof ApiClientError) {
27712
+ switch (err.errorCode) {
27713
+ case "invalid_format":
27714
+ console.error(`${icons2.error} Invalid handle format: ${err.message}`);
27715
+ break;
27716
+ case "handle_taken":
27717
+ console.error(`${icons2.error} @${name} is already taken`);
27718
+ break;
27719
+ case "handle_reserved":
27720
+ console.error(`${icons2.error} @${name} is reserved`);
27721
+ break;
27722
+ case "handle_limit":
27723
+ console.error(`${icons2.error} You've reached the maximum of 5 owned handles`);
27724
+ console.log(` You can be a member of unlimited handles owned by others.`);
27725
+ break;
27726
+ case "rate_limit":
27727
+ console.error(`${icons2.error} Rate limit: max 3 handle claims per 30 days`);
27728
+ break;
27729
+ default:
27730
+ console.error(`${icons2.error} ${err.message}`);
27731
+ }
27732
+ process.exit(1);
27733
+ }
27734
+ throw err;
27735
+ }
27736
+ }
27737
+ });
27738
+ var listCommand3 = defineCommand({
27739
+ meta: {
27740
+ name: "list",
27741
+ description: "List your handles"
27742
+ },
27743
+ args: {
27744
+ refresh: {
27745
+ type: "boolean",
27746
+ description: "Refresh from API",
27747
+ default: false
27748
+ }
27749
+ },
27750
+ async run({ args }) {
27751
+ const auth = await getAuth();
27752
+ if (!auth) {
27753
+ console.error(`${icons2.error} Not logged in. Run ${c3.cmd("aspects login")} first.`);
27754
+ process.exit(1);
27755
+ }
27756
+ let handles = await getHandles();
27757
+ if (args.refresh) {
27758
+ try {
27759
+ const account = await getAccount();
27760
+ await updateHandles(account.handles);
27761
+ handles = account.handles;
27762
+ } catch (err) {
27763
+ console.error(`${icons2.warn} Could not refresh from API: ${err.message}`);
27764
+ }
27765
+ }
27766
+ if (handles.length === 0) {
27767
+ console.log(`${icons2.info} You don't have any handles yet.`);
27768
+ console.log(` Run ${c3.cmd("aspects handle claim <name>")} to claim one.`);
27769
+ return;
27770
+ }
27771
+ console.log();
27772
+ console.log(`${icons2.info} Your Handles`);
27773
+ console.log();
27774
+ for (const handle of handles) {
27775
+ const defaultTag = handle.default ? c3.success(" (default)") : "";
27776
+ const roleTag = c3.dim(` ${handle.role}`);
27777
+ console.log(` @${handle.name}${roleTag}${defaultTag}`);
27778
+ }
27779
+ const ownedCount = handles.filter((h4) => h4.role === "owner").length;
27780
+ console.log();
27781
+ console.log(` ${c3.dim(`${ownedCount}/5 owned handles`)}`);
27782
+ console.log();
27783
+ }
27784
+ });
27785
+ var defaultCommand = defineCommand({
27786
+ meta: {
27787
+ name: "default",
27788
+ description: "Set your default publishing handle"
27789
+ },
27790
+ args: {
27791
+ name: {
27792
+ type: "positional",
27793
+ description: "Handle to set as default",
27794
+ required: true
27795
+ }
27796
+ },
27797
+ async run({ args }) {
27798
+ const auth = await getAuth();
27799
+ if (!auth) {
27800
+ console.error(`${icons2.error} Not logged in. Run ${c3.cmd("aspects login")} first.`);
27801
+ process.exit(1);
27802
+ }
27803
+ const name = args.name.toLowerCase().replace(/^@/, "");
27804
+ const handles = await getHandles();
27805
+ const hasHandle = handles.some((h4) => h4.name === name);
27806
+ if (!hasHandle) {
27807
+ console.error(`${icons2.error} You don't have access to @${name}`);
27808
+ console.log();
27809
+ console.log(" Your handles:");
27810
+ for (const h4 of handles) {
27811
+ console.log(` @${h4.name}`);
27812
+ }
27813
+ process.exit(1);
27814
+ }
27815
+ try {
27816
+ await setDefaultHandleApi(name);
27817
+ await setDefaultHandle(name);
27818
+ console.log(`${icons2.success} Default handle set to @${name}`);
27819
+ } catch (err) {
27820
+ if (err instanceof ApiClientError) {
27821
+ console.error(`${icons2.error} ${err.message}`);
27822
+ process.exit(1);
27823
+ }
27824
+ throw err;
27825
+ }
27826
+ }
27827
+ });
27828
+ var checkCommand = defineCommand({
27829
+ meta: {
27830
+ name: "check",
27831
+ description: "Check if a handle is available"
27832
+ },
27833
+ args: {
27834
+ name: {
27835
+ type: "positional",
27836
+ description: "Handle name to check",
27837
+ required: true
27838
+ }
27839
+ },
27840
+ async run({ args }) {
27841
+ const name = args.name.toLowerCase().replace(/^@/, "");
27842
+ const formatError2 = validateHandleFormat(name);
27843
+ if (formatError2) {
27844
+ console.error(`${icons2.error} ${formatError2}`);
27845
+ process.exit(1);
27846
+ }
27847
+ try {
27848
+ const result = await checkHandleAvailability(name);
27849
+ if (result.available) {
27850
+ console.log(`${icons2.success} @${name} is available`);
27851
+ } else {
27852
+ console.log(`${icons2.error} @${name} is not available`);
27853
+ if (result.reason) {
27854
+ switch (result.reason) {
27855
+ case "taken":
27856
+ console.log(` This handle is already claimed.`);
27857
+ break;
27858
+ case "reserved":
27859
+ console.log(` This handle is reserved.`);
27860
+ break;
27861
+ case "invalid":
27862
+ console.log(` This handle format is not allowed.`);
27863
+ break;
27864
+ default:
27865
+ console.log(` Reason: ${result.reason}`);
27866
+ }
27867
+ }
27868
+ }
27869
+ } catch (err) {
27870
+ if (err instanceof ApiClientError) {
27871
+ console.error(`${icons2.error} ${err.message}`);
27872
+ process.exit(1);
27873
+ }
27874
+ throw err;
27875
+ }
27876
+ }
27877
+ });
27878
+ var handle_default = defineCommand({
27879
+ meta: {
27880
+ name: "handle",
27881
+ description: "Manage your handles (namespaces for publishing)"
27882
+ },
27883
+ subCommands: {
27884
+ claim: claimCommand,
27885
+ list: listCommand3,
27886
+ default: defaultCommand,
27887
+ check: checkCommand
27888
+ }
27889
+ });
27890
+
27891
+ // src/commands/whoami.ts
27892
+ var whoami_default = defineCommand({
27893
+ meta: {
27894
+ name: "whoami",
27895
+ description: "Show your current identity"
27896
+ },
27897
+ async run() {
27898
+ const auth = await getAuth();
27899
+ if (!auth) {
27900
+ console.log(`${icons2.info} Not logged in`);
27901
+ console.log(` Run ${c3.cmd("aspects login")} to authenticate.`);
27902
+ return;
27903
+ }
27904
+ const defaultHandle = await getDefaultHandle();
27905
+ const handles = await getHandles();
27906
+ console.log();
27907
+ if (defaultHandle) {
27908
+ console.log(` ${c3.bold(`@${defaultHandle}`)} ${c3.dim("(default)")}`);
27909
+ } else if (handles.length === 0) {
27910
+ console.log(` ${c3.warn("No handles claimed")}`);
27911
+ console.log(` Run ${c3.cmd("aspects handle claim <name>")} to claim a handle.`);
27912
+ return;
27913
+ }
27914
+ if (handles.length > 0) {
27915
+ console.log();
27916
+ console.log(` ${c3.bold("Handles")}`);
27917
+ for (const handle of handles) {
27918
+ const isDefault = handle.name === defaultHandle;
27919
+ const roleColor = handle.role === "owner" ? c3.success : c3.dim;
27920
+ const defaultIndicator = isDefault ? c3.success(" *") : "";
27921
+ console.log(` @${handle.name} ${roleColor(handle.role)}${defaultIndicator}`);
27922
+ }
27923
+ }
27924
+ const ownedCount = handles.filter((h4) => h4.role === "owner").length;
27925
+ console.log();
27926
+ console.log(` ${c3.dim(`${ownedCount}/5 owned handles`)}`);
27927
+ const expiresAt = new Date(auth.expiresAt);
27928
+ const isExpired = expiresAt < new Date;
27929
+ if (isExpired) {
27930
+ console.log(` ${c3.warn("Token expired")} - run ${c3.cmd("aspects login")} to refresh`);
27931
+ }
27932
+ console.log();
27933
+ }
27934
+ });
27935
+
26702
27936
  // src/cli.ts
27937
+ var VERSION = package_default.version;
26703
27938
  var ALIASES = {
26704
27939
  c: "create",
26705
27940
  new: "create",
@@ -26708,7 +27943,8 @@ var ALIASES = {
26708
27943
  get: "add",
26709
27944
  a: "add",
26710
27945
  i: "add",
26711
- g: "add"
27946
+ g: "add",
27947
+ h: "handle"
26712
27948
  };
26713
27949
  var ALIAS_HINTS = {};
26714
27950
  for (const [alias, canonical] of Object.entries(ALIASES)) {
@@ -26718,7 +27954,14 @@ var cmdArg = process.argv[2];
26718
27954
  if (cmdArg && ALIASES[cmdArg]) {
26719
27955
  process.argv[2] = ALIASES[cmdArg];
26720
27956
  }
27957
+ if (process.argv[2] === "config" && !process.argv[3]) {
27958
+ process.argv.splice(3, 0, "list");
27959
+ }
27960
+ if (process.argv[2] === "handle" && !process.argv[3]) {
27961
+ process.argv.splice(3, 0, "list");
27962
+ }
26721
27963
  var COMMANDS = [
27964
+ { name: "init", cmd: init_default, desc: "Initialize project for local aspects" },
26722
27965
  { name: "create", cmd: create_default, desc: "Create a new aspect interactively", aliases: ["c", "new", "n"] },
26723
27966
  { name: "add", cmd: add_default, desc: "Install aspect(s) to your local library", aliases: ["install", "get", "a", "i", "g"] },
26724
27967
  { name: "list", cmd: list_default, desc: "List installed aspects" },
@@ -26736,14 +27979,42 @@ var COMMANDS = [
26736
27979
  { name: "share", cmd: share_default, desc: "Share an aspect via content hash" },
26737
27980
  { name: "unpublish", cmd: unpublish_default, desc: "Unpublish a version from the registry" },
26738
27981
  { name: "login", cmd: login_default, desc: "Authenticate with the registry" },
26739
- { name: "logout", cmd: logout_default, desc: "Clear stored authentication tokens" }
27982
+ { name: "logout", cmd: logout_default, desc: "Clear stored authentication tokens" },
27983
+ { name: "handle", cmd: handle_default, desc: "Manage your handles (namespaces)", aliases: ["h"] },
27984
+ { name: "whoami", cmd: whoami_default, desc: "Show your current identity" },
27985
+ { name: "config", cmd: config_default, desc: "View and modify configuration" }
26740
27986
  ];
26741
27987
  function showCustomHelp() {
26742
27988
  morphistBanner();
26743
- console.log(import_picocolors5.default.dim(`Package manager for AI personality aspects (v0.1.0)`));
27989
+ console.log(import_picocolors5.default.dim(`Package manager for AI personality aspects (v${VERSION})`));
26744
27990
  console.log();
26745
27991
  console.log(`${import_picocolors5.default.bold(import_picocolors5.default.underline("USAGE"))} ${import_picocolors5.default.cyan("aspects")} ${import_picocolors5.default.dim("<command>")} ${import_picocolors5.default.dim("[options]")}`);
26746
27992
  console.log();
27993
+ console.log(import_picocolors5.default.bold(import_picocolors5.default.underline("QUICK START")));
27994
+ console.log();
27995
+ console.log(` ${import_picocolors5.default.cyan("aspects add alaric")} Install an aspect from the registry`);
27996
+ console.log(` ${import_picocolors5.default.cyan("aspects create")} Create a new aspect interactively`);
27997
+ console.log(` ${import_picocolors5.default.cyan("aspects search wizard")} Search for aspects`);
27998
+ console.log();
27999
+ console.log(import_picocolors5.default.bold(import_picocolors5.default.underline("EXAMPLE: CREATE & SHARE (NO ACCOUNT)")));
28000
+ console.log();
28001
+ console.log(` ${import_picocolors5.default.dim("1.")} ${import_picocolors5.default.cyan("aspects create my-aspect")} Create aspect interactively`);
28002
+ console.log(` ${import_picocolors5.default.dim("2.")} ${import_picocolors5.default.dim("Edit")} ./my-aspect/aspect.json ${import_picocolors5.default.dim("(customize prompt, add directives)")}`);
28003
+ console.log(` ${import_picocolors5.default.dim("3.")} ${import_picocolors5.default.cyan("aspects share ./my-aspect")} Share to registry, get hash`);
28004
+ console.log(` ${import_picocolors5.default.dim("4.")} ${import_picocolors5.default.dim("Share hash with others:")} ${import_picocolors5.default.cyan("aspects add blake3:<hash>")}`);
28005
+ console.log();
28006
+ console.log(import_picocolors5.default.bold(import_picocolors5.default.underline("PUBLISHING")));
28007
+ console.log();
28008
+ console.log(` ${import_picocolors5.default.cyan("aspects share")} ${import_picocolors5.default.dim("No account")} - Share via content hash`);
28009
+ console.log(` Anyone installs with: ${import_picocolors5.default.cyan("aspects add blake3:<hash>")}`);
28010
+ console.log(` Quick, anonymous, no versioning`);
28011
+ console.log();
28012
+ console.log(` ${import_picocolors5.default.cyan("aspects publish")} ${import_picocolors5.default.dim("Account required")} - Claim a name`);
28013
+ console.log(` Anyone installs with: ${import_picocolors5.default.cyan("aspects add <name>")}`);
28014
+ console.log(` Versioning, updates, publisher reputation`);
28015
+ console.log();
28016
+ console.log(` ${import_picocolors5.default.cyan("aspects login")} Create account or authenticate`);
28017
+ console.log();
26747
28018
  console.log(import_picocolors5.default.bold(import_picocolors5.default.underline("COMMANDS")));
26748
28019
  console.log();
26749
28020
  const maxNameLen = Math.max(...COMMANDS.map((c5) => c5.name.length));
@@ -26753,7 +28024,15 @@ function showCustomHelp() {
26753
28024
  console.log(` ${import_picocolors5.default.cyan(paddedName)} ${desc}${aliasPart}`);
26754
28025
  }
26755
28026
  console.log();
26756
- console.log(`Use ${import_picocolors5.default.cyan("aspects <command> --help")} for more info.`);
28027
+ console.log(import_picocolors5.default.bold(import_picocolors5.default.underline("CONCEPTS")));
28028
+ console.log();
28029
+ console.log(` ${import_picocolors5.default.bold("Directives")} Strict MUST-follow rules with priority levels`);
28030
+ console.log(` Emphasized across all LLM models (XML, bold, repetition)`);
28031
+ console.log();
28032
+ console.log(` ${import_picocolors5.default.bold("Instructions")} Softer guidance and preferences`);
28033
+ console.log(` General behavioral hints, not strictly enforced`);
28034
+ console.log();
28035
+ console.log(`Use ${import_picocolors5.default.cyan("aspects <command> --help")} for detailed command help.`);
26757
28036
  console.log();
26758
28037
  }
26759
28038
  var showingHelp = process.argv.length === 2 || process.argv.length === 3 && (process.argv[2] === "--help" || process.argv[2] === "-h");
@@ -26768,7 +28047,7 @@ for (const { name, cmd } of COMMANDS) {
26768
28047
  var main = defineCommand({
26769
28048
  meta: {
26770
28049
  name: "aspects",
26771
- version: "0.1.0",
28050
+ version: VERSION,
26772
28051
  description: "Package manager for AI personality aspects"
26773
28052
  },
26774
28053
  subCommands