@aident-ai/cli 0.1.8 → 0.2.0-rc.0

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.
Files changed (3) hide show
  1. package/README.md +9 -2
  2. package/dist/cli.mjs +145 -31
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -97,8 +97,12 @@ providers to Loadout integrations, and starts selected Vault/OAuth connect flows
97
97
 
98
98
  `skills favorite` saves the account preference and immediately synchronizes all current favorites. `skills sync`
99
99
  reconciles each current public revision into `~/.agents/skills` and links it into supported harness-global Skill
100
- directories, where the deterministic `loadout-<slug>-<id>` name is available as a slash command. The CLI tracks its
101
- owned paths in `~/.aident/favorite-skills.json` and never overwrites or removes unmanaged paths.
100
+ directories, where the deterministic `loadout-<slug>-<id>` name is available as a slash command. Its discovery
101
+ description uses the reviewed display name and summary for the CLI's system language, with English fallback. The CLI
102
+ tracks its owned paths in `~/.aident/favorite-skills.json` and never overwrites or removes unmanaged paths. During
103
+ normal authenticated commands, the server compares the manifest state with the user's current favorites. When they
104
+ differ, the CLI tells the agent to finish the current task and ask the user before running `aident skills sync`; it
105
+ never synchronizes favorite Skills automatically.
102
106
 
103
107
  `billing balance` keeps `balance` and returns `currentCreditSource` for the currently selected wallet. The legacy
104
108
  `creditSource` alias remains available for compatibility. The response also returns `wallets.personal` plus
@@ -109,6 +113,9 @@ uncharged and historical rows return `null`. Team owners can pass `--scope team`
109
113
  active members instead of just their own: each entry adds `actingUserId`/`actingUserEmail`/`actingUserName`, and the
110
114
  summary adds a `byActingUser` breakdown. `--scope team` is rejected for anyone who isn't the owner of a team with
111
115
  credit sharing enabled; the default `--scope mine` behavior is unchanged.
116
+ Successful self-owned rows can include `resultFiles` with a direct `downloadUrl` and nullable `expiresAt`, allowing a
117
+ caller to recover persisted output after an interrupted response. Audit output does not expose internal asset IDs, and
118
+ team scope does not expose another member's result files.
112
119
 
113
120
  ## Configuration
114
121
 
package/dist/cli.mjs CHANGED
@@ -88,12 +88,12 @@ async function login(options) {
88
88
  const baseUrl = options.baseUrl.replace(/\/+$/, "");
89
89
  const writeInfo = options.format === "json" ? logErr : logInfo;
90
90
  if (options.oob)
91
- return loginOob(baseUrl, writeInfo, options.format === "json" ? process.stderr : process.stdout);
91
+ return loginOob(baseUrl, writeInfo, options.format === "json" ? process.stderr : process.stdout, options.ref);
92
92
  try {
93
- return await loginLoopback(baseUrl, writeInfo);
93
+ return await loginLoopback(baseUrl, writeInfo, options.ref);
94
94
  } catch (err) {
95
95
  logErr(`Loopback OAuth failed (${err instanceof Error ? err.message : String(err)}). Falling back to OOB flow.`);
96
- return loginOob(baseUrl, writeInfo, options.format === "json" ? process.stderr : process.stdout);
96
+ return loginOob(baseUrl, writeInfo, options.format === "json" ? process.stderr : process.stdout, options.ref);
97
97
  }
98
98
  }
99
99
  async function refreshToken(creds) {
@@ -128,7 +128,7 @@ async function logout(creds) {
128
128
  return;
129
129
  });
130
130
  }
131
- async function loginLoopback(baseUrl, writeInfo) {
131
+ async function loginLoopback(baseUrl, writeInfo, ref) {
132
132
  const verifier = base64UrlEncode(randomBytes(48));
133
133
  const challenge = base64UrlEncode(createHash("sha256").update(verifier).digest());
134
134
  const state = base64UrlEncode(randomBytes(16));
@@ -143,6 +143,8 @@ async function loginLoopback(baseUrl, writeInfo) {
143
143
  authorizeUrl.searchParams.set("code_challenge", challenge);
144
144
  authorizeUrl.searchParams.set("code_challenge_method", "S256");
145
145
  authorizeUrl.searchParams.set("state", state);
146
+ if (ref)
147
+ authorizeUrl.searchParams.set("via", ref);
146
148
  const authorizeUrlString = authorizeUrl.toString();
147
149
  setExpectedOpenRedirectUrl(authorizeUrlString);
148
150
  const loginOpenUrl = buildLoginOpenUrl(port, state, authorizeUrlString);
@@ -158,7 +160,7 @@ async function loginLoopback(baseUrl, writeInfo) {
158
160
  const tok = await exchangeCode(baseUrl, clientId, code, redirectUri, verifier);
159
161
  return buildCreds(baseUrl, clientId, tok);
160
162
  }
161
- async function loginOob(baseUrl, writeInfo, promptOutput) {
163
+ async function loginOob(baseUrl, writeInfo, promptOutput, ref) {
162
164
  const redirectUri = `${baseUrl}/mcp/oob`;
163
165
  const verifier = base64UrlEncode(randomBytes(48));
164
166
  const challenge = base64UrlEncode(createHash("sha256").update(verifier).digest());
@@ -174,6 +176,8 @@ async function loginOob(baseUrl, writeInfo, promptOutput) {
174
176
  authorizeUrl.searchParams.set("code_challenge", challenge);
175
177
  authorizeUrl.searchParams.set("code_challenge_method", "S256");
176
178
  authorizeUrl.searchParams.set("state", state);
179
+ if (ref)
180
+ authorizeUrl.searchParams.set("via", ref);
177
181
  const authorizeUrlString = authorizeUrl.toString();
178
182
  writeInfo(`Opening browser for Aident login...`);
179
183
  writeInfo(`If the browser does not open, visit: ${authorizeUrlString}`);
@@ -598,7 +602,7 @@ function normalizeBaseUrl(url) {
598
602
  }
599
603
 
600
604
  // src/version.ts
601
- var VERSION = "0.1.8";
605
+ var VERSION = "0.2.0-rc.0";
602
606
 
603
607
  // src/catalogCache.ts
604
608
  var CACHE_TTL_MS = 5 * 60 * 1000;
@@ -628,7 +632,13 @@ async function genWriteCachedCatalog(params, catalog) {
628
632
  } catch {}
629
633
  }
630
634
  function getCacheFile(params) {
631
- const key = crypto.createHash("sha256").update([params.baseUrl, params.packageName, params.installedSkillVersion ?? "", VERSION].join("\x00")).digest("hex").slice(0, 16);
635
+ const key = crypto.createHash("sha256").update([
636
+ params.baseUrl,
637
+ params.packageName,
638
+ params.installedSkillVersion ?? "",
639
+ params.favoriteSkillsStateToken ?? "",
640
+ VERSION
641
+ ].join("\x00")).digest("hex").slice(0, 16);
632
642
  return join2(getAidentDir(), "cache", `catalog-${key}.json`);
633
643
  }
634
644
  function getCredentialFingerprint(accessToken) {
@@ -642,7 +652,7 @@ function isCommandCatalog(value) {
642
652
  }
643
653
 
644
654
  // src/loadoutSkill.ts
645
- import { createHash as createHash2 } from "node:crypto";
655
+ import { createHash as createHash3 } from "node:crypto";
646
656
  import { lstat as lstat2, readdir as readdir2, readFile as readFile5, realpath as realpath2 } from "node:fs/promises";
647
657
  import { basename, dirname as dirname2, join as join6 } from "node:path";
648
658
 
@@ -5552,6 +5562,20 @@ async function isExecutable(path) {
5552
5562
  }
5553
5563
  }
5554
5564
 
5565
+ // ../web/shared/integrations/LoadoutFavoriteSkillSyncState.ts
5566
+ import { createHash as createHash2 } from "node:crypto";
5567
+ var AIDENT_FAVORITE_SKILLS_STATE_HEADER = "x-aident-favorite-skills-state";
5568
+ var LoadoutFavoriteSkillsStateTokenSchema = zod_default.string().regex(/^[a-f0-9]{64}$/);
5569
+ var LoadoutFavoriteSkillsUpdateSchema = zod_default.object({
5570
+ stateToken: LoadoutFavoriteSkillsStateTokenSchema,
5571
+ favoriteCount: zod_default.number().int().nonnegative(),
5572
+ syncRequired: zod_default.boolean()
5573
+ }).strict();
5574
+ function getLoadoutFavoriteSkillsStateToken(skills) {
5575
+ const state = skills.map(({ name, artifactVersionId }) => ({ name, artifactVersionId })).sort((left, right) => left.name.localeCompare(right.name));
5576
+ return createHash2("sha256").update(JSON.stringify(state)).digest("hex");
5577
+ }
5578
+
5555
5579
  // ../web/shared/loadout/LoadoutUpdatePolicy.ts
5556
5580
  var AIDENT_UPDATE_PROTOCOL_HEADER = "x-aident-update-protocol";
5557
5581
  var AIDENT_UPDATE_PROTOCOL_VERSION = "1";
@@ -5712,6 +5736,40 @@ var KNOWN_CLEAN_LOADOUT_SKILL_REQUIRED_FILE_GIT_BLOB_SHA1 = {
5712
5736
  "references/mcp.md": "4040ef157a84d9159bc8bbecd288741fd51dc751",
5713
5737
  "references/troubleshooting.md": "f819915c8829b028630ef1f8f29ae9433a072ca8"
5714
5738
  }
5739
+ ],
5740
+ "0.4.12": [
5741
+ {
5742
+ "SKILL.md": "3808426c0802578a27e3ad984b24f546aa8cbe40",
5743
+ "references/api.md": "fdd5a23a72e0afd2809472d004961f11e1959bd4",
5744
+ "references/loadout.md": "3f6791826535bf458cb1ddcd24aee003af98a50d",
5745
+ "references/mcp.md": "3e92c88b1ef94e863bd4ce977a36abc3a0df2138",
5746
+ "references/troubleshooting.md": "f819915c8829b028630ef1f8f29ae9433a072ca8"
5747
+ },
5748
+ {
5749
+ "SKILL.md": "3808426c0802578a27e3ad984b24f546aa8cbe40",
5750
+ "references/api.md": "a6a4ff7e106245697a1a6527f0ff0d3df729bfb3",
5751
+ "references/loadout.md": "aa68ff08dafd97a268279b7cab532b3a4ce9d175",
5752
+ "references/mcp.md": "3e92c88b1ef94e863bd4ce977a36abc3a0df2138",
5753
+ "references/troubleshooting.md": "f819915c8829b028630ef1f8f29ae9433a072ca8"
5754
+ }
5755
+ ],
5756
+ "0.4.13": [
5757
+ {
5758
+ "SKILL.md": "abf6057514737bbf3ca4cffc511919fc254c87c2",
5759
+ "references/api.md": "a6a4ff7e106245697a1a6527f0ff0d3df729bfb3",
5760
+ "references/loadout.md": "25a696a0107055e250424b117cee34d282784935",
5761
+ "references/mcp.md": "3e92c88b1ef94e863bd4ce977a36abc3a0df2138",
5762
+ "references/troubleshooting.md": "f819915c8829b028630ef1f8f29ae9433a072ca8"
5763
+ }
5764
+ ],
5765
+ "0.4.14": [
5766
+ {
5767
+ "SKILL.md": "61d825a96dab110426d92cb80feb5fe40f7a010b",
5768
+ "references/api.md": "240b913e8c85492f2441326033db91747c54863e",
5769
+ "references/loadout.md": "be4dbd9e0aad75afe647800f64d2bb16c22d03a8",
5770
+ "references/mcp.md": "3e92c88b1ef94e863bd4ce977a36abc3a0df2138",
5771
+ "references/troubleshooting.md": "f819915c8829b028630ef1f8f29ae9433a072ca8"
5772
+ }
5715
5773
  ]
5716
5774
  };
5717
5775
  async function fetchLoadoutSkillMetadata(baseUrl) {
@@ -5734,7 +5792,7 @@ function isLoadoutSkillMetadata(value) {
5734
5792
  if (!value || typeof value !== "object")
5735
5793
  return false;
5736
5794
  const body = value;
5737
- return body.product === "loadout" && typeof body.skillUrl === "string" && typeof body.skillVersion === "string" && typeof body.updatedAt === "string" && typeof body.setupPrompt === "string" && typeof body.updatePrompt === "string" && typeof body.minCliVersion === "string" && typeof body.recommendedCliVersion === "string" && (body.localIntegrationMigrationPromptEnabled === undefined || typeof body.localIntegrationMigrationPromptEnabled === "boolean") && (body.updatePolicy === undefined || LoadoutUpdatePolicySchema.safeParse(body.updatePolicy).success) && Array.isArray(body.notices) && body.notices.every(isLoadoutSkillNotice);
5795
+ return body.product === "loadout" && typeof body.skillUrl === "string" && typeof body.skillVersion === "string" && typeof body.updatedAt === "string" && typeof body.setupPrompt === "string" && typeof body.updatePrompt === "string" && typeof body.minCliVersion === "string" && typeof body.recommendedCliVersion === "string" && (body.localIntegrationMigrationPromptEnabled === undefined || typeof body.localIntegrationMigrationPromptEnabled === "boolean") && (body.updatePolicy === undefined || LoadoutUpdatePolicySchema.safeParse(body.updatePolicy).success) && (body.favoriteSkills === undefined || LoadoutFavoriteSkillsUpdateSchema.safeParse(body.favoriteSkills).success) && Array.isArray(body.notices) && body.notices.every(isLoadoutSkillNotice);
5738
5796
  }
5739
5797
  function isLocalIntegrationMigrationPromptEnabled(metadata) {
5740
5798
  return metadata?.localIntegrationMigrationPromptEnabled === true;
@@ -5844,7 +5902,7 @@ async function inspectLoadoutSkillDirectory(skillDirectory, managedReleasesRoot
5844
5902
  };
5845
5903
  }
5846
5904
  function computeLoadoutSkillRequiredFileDigest(files) {
5847
- const hash = createHash2("sha256");
5905
+ const hash = createHash3("sha256");
5848
5906
  for (const path of LOADOUT_SKILL_REQUIRED_FILES) {
5849
5907
  const content = getFileContent(files, path);
5850
5908
  if (content === undefined)
@@ -5867,7 +5925,7 @@ function computeLoadoutSkillArtifactSha256(files, version) {
5867
5925
  return { path, content };
5868
5926
  })
5869
5927
  });
5870
- return createHash2("sha256").update(body).digest("hex");
5928
+ return createHash3("sha256").update(body).digest("hex");
5871
5929
  }
5872
5930
  function validateLoadoutSkillRequiredFileContents(files, expectedVersion) {
5873
5931
  const frontmatter = parseSkillFrontmatter(getFileContent(files, "SKILL.md") ?? "");
@@ -5961,7 +6019,7 @@ function getFileContent(files, path) {
5961
6019
  return files[path];
5962
6020
  }
5963
6021
  function computeGitBlobSha1(content) {
5964
- return createHash2("sha1").update(`blob ${content.byteLength}\x00`).update(content).digest("hex");
6022
+ return createHash3("sha1").update(`blob ${content.byteLength}\x00`).update(content).digest("hex");
5965
6023
  }
5966
6024
  function isLoadoutSkillNotice(value) {
5967
6025
  if (!value || typeof value !== "object")
@@ -5972,6 +6030,7 @@ function isLoadoutSkillNotice(value) {
5972
6030
 
5973
6031
  // src/client.ts
5974
6032
  var CLI_CATALOG_DURATION_HEADER = "x-aident-cli-catalog-duration-ms";
6033
+ var CLI_LOCALE = Intl.DateTimeFormat().resolvedOptions().locale;
5975
6034
 
5976
6035
  class CliClient {
5977
6036
  creds;
@@ -5979,9 +6038,10 @@ class CliClient {
5979
6038
  packages;
5980
6039
  installedSkillVersion;
5981
6040
  loadoutSkillInventory;
6041
+ favoriteSkillsStateToken;
5982
6042
  commandOperations = new Map;
5983
6043
  catalogDurationMs = null;
5984
- constructor(creds, credentialSource = "stored", packages, installedSkillVersion = null, loadoutSkillInventory = null) {
6044
+ constructor(creds, credentialSource = "stored", packages, installedSkillVersion = null, loadoutSkillInventory = null, favoriteSkillsStateToken = null) {
5985
6045
  this.creds = creds;
5986
6046
  this.credentialSource = credentialSource;
5987
6047
  if (packages.length === 0)
@@ -5989,6 +6049,7 @@ class CliClient {
5989
6049
  this.packages = [...packages];
5990
6050
  this.installedSkillVersion = installedSkillVersion;
5991
6051
  this.loadoutSkillInventory = loadoutSkillInventory;
6052
+ this.favoriteSkillsStateToken = favoriteSkillsStateToken;
5992
6053
  }
5993
6054
  get baseUrl() {
5994
6055
  return this.creds.base_url.replace(/\/+$/, "");
@@ -6059,13 +6120,15 @@ class CliClient {
6059
6120
  accessToken: this.creds.access_token,
6060
6121
  baseUrl: this.baseUrl,
6061
6122
  packageName,
6062
- installedSkillVersion: this.installedSkillVersion
6123
+ installedSkillVersion: this.installedSkillVersion,
6124
+ favoriteSkillsStateToken: this.favoriteSkillsStateToken
6063
6125
  };
6064
6126
  const cached = await genReadCachedCatalog(cacheParams);
6065
6127
  if (cached)
6066
6128
  return { status: 200, body: withValidLoadoutSkillMetadata(cached) };
6067
6129
  const headers = packageName === "loadout" ? {
6068
- ...this.installedSkillVersion ? { "x-aident-skill-version": this.installedSkillVersion } : {}
6130
+ ...this.installedSkillVersion ? { "x-aident-skill-version": this.installedSkillVersion } : {},
6131
+ ...this.favoriteSkillsStateToken ? { [AIDENT_FAVORITE_SKILLS_STATE_HEADER]: this.favoriteSkillsStateToken } : {}
6069
6132
  } : undefined;
6070
6133
  const path = this.catalogPath(packageName);
6071
6134
  let result;
@@ -6109,6 +6172,7 @@ class CliClient {
6109
6172
  async fetchJson(method, path, body, extraHeaders) {
6110
6173
  const headers = {
6111
6174
  Authorization: `Bearer ${this.creds.access_token}`,
6175
+ "Accept-Language": CLI_LOCALE,
6112
6176
  "User-Agent": `@aident-ai/cli/${VERSION}`,
6113
6177
  [AIDENT_UPDATE_PROTOCOL_HEADER]: AIDENT_UPDATE_PROTOCOL_VERSION,
6114
6178
  ...extraHeaders
@@ -6474,7 +6538,7 @@ function getLocalHelp(version) {
6474
6538
  "aident config get <key>",
6475
6539
  "aident packages add <playbook|intern>",
6476
6540
  "aident doctor",
6477
- "aident setup [--client-name <name>]",
6541
+ "aident setup [--client-name <name>] [--ref <code>]",
6478
6542
  "aident integrations migrate-local",
6479
6543
  "aident <domain> <command> [--flag value ...] [--json]"
6480
6544
  ],
@@ -6663,13 +6727,19 @@ function renderCommandHelp(catalog, domain, command, subcommand) {
6663
6727
  }
6664
6728
 
6665
6729
  // src/loadoutFavoriteSkills.ts
6666
- import { createHash as createHash3 } from "node:crypto";
6730
+ import { createHash as createHash4 } from "node:crypto";
6667
6731
  import { lstat as lstat3, mkdir as mkdir6, readFile as readFile7, readlink, rename as rename2, rm as rm2, symlink, writeFile as writeFile6 } from "node:fs/promises";
6668
6732
  import { homedir as homedir4 } from "node:os";
6669
6733
  import path from "node:path";
6670
6734
 
6671
6735
  // ../web/shared/integrations/LoadoutSkillConstants.ts
6672
6736
  var LOADOUT_SKILL_ENTRYPOINT = "SKILL.md";
6737
+ var LOADOUT_SKILL_PLATFORM_SECTION_HEADING = "Aident Loadout Platform";
6738
+ var LOADOUT_SKILL_PLATFORM_SECTION = `## **${LOADOUT_SKILL_PLATFORM_SECTION_HEADING}**
6739
+
6740
+ This Skill is authored, published, and distributed through [**Aident Loadout**](https://loadout.aident.ai/), a platform that enables agents to connect securely with more than 1,000 popular apps and tools.
6741
+
6742
+ Before using this Skill, verify that the Aident Loadout Skill, CLI, Plugin, or MCP is installed globally in the current environment. If Aident Loadout is already available, continue with this Skill and complete any setup required by the selected integrations. If it is unavailable or its installation status is uncertain, follow the [**Aident Loadout setup instructions**](https://aident.ai/SETUP.md). After installation is verified, return to the campaign brief and continue.`;
6673
6743
  var LOADOUT_SKILL_NAME_PATTERN = /^skill:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
6674
6744
  var LOADOUT_SKILL_MAX_FILE_COUNT = 50;
6675
6745
  var LOADOUT_SKILL_MAX_PACKAGE_BYTES = 256 * 1024;
@@ -6731,7 +6801,7 @@ function assertPackage(pkg) {
6731
6801
  packageBytes += bytes;
6732
6802
  if (bytes !== file.sizeBytes)
6733
6803
  throw new Error(`Size mismatch: ${pkg.name}/${file.path}`);
6734
- if (createHash3("sha256").update(file.content).digest("hex") !== file.contentDigest) {
6804
+ if (createHash4("sha256").update(file.content).digest("hex") !== file.contentDigest) {
6735
6805
  throw new Error(`Digest mismatch: ${pkg.name}/${file.path}`);
6736
6806
  }
6737
6807
  }
@@ -6754,7 +6824,7 @@ function entrypointContent(pkg, content) {
6754
6824
  return [
6755
6825
  "---",
6756
6826
  `name: ${quoteYaml(commandName(pkg))}`,
6757
- `description: ${quoteYaml(pkg.summary)}`,
6827
+ `description: ${quoteYaml(`${pkg.displayName}: ${pkg.summary}`)}`,
6758
6828
  "---",
6759
6829
  `<!-- aident-loadout-skill name=${pkg.name} artifact-version=${pkg.artifactVersionId} -->`,
6760
6830
  "",
@@ -6779,7 +6849,7 @@ async function readManifest(home, env) {
6779
6849
  throw new Error("Unsupported manifest");
6780
6850
  const harnessRoots = new Set(getHarnessHosts(home, env).map(({ root }) => path.join(root, "skills")));
6781
6851
  for (const skill of value.skills) {
6782
- if (!LOADOUT_SKILL_NAME_PATTERN.test(skill.name) || !isCanonicalUuid(skill.artifactVersionId) || !/^loadout-[a-z0-9-]+-[0-9a-f]{8}$/.test(skill.commandName) || skill.installPath !== path.join(getCanonicalRoot(home, env), skill.commandName) || skill.linkPaths.some((linkPath) => !harnessRoots.has(path.dirname(linkPath)) || path.basename(linkPath) !== skill.commandName)) {
6852
+ if (!LOADOUT_SKILL_NAME_PATTERN.test(skill.name) || !isCanonicalUuid(skill.artifactVersionId) || skill.displayName !== undefined && typeof skill.displayName !== "string" || skill.summary !== undefined && typeof skill.summary !== "string" || !/^loadout-[a-z0-9-]+-[0-9a-f]{8}$/.test(skill.commandName) || skill.installPath !== path.join(getCanonicalRoot(home, env), skill.commandName) || skill.linkPaths.some((linkPath) => !harnessRoots.has(path.dirname(linkPath)) || path.basename(linkPath) !== skill.commandName)) {
6783
6853
  throw new Error("Invalid favorite Skills ownership manifest");
6784
6854
  }
6785
6855
  }
@@ -6790,6 +6860,10 @@ async function readManifest(home, env) {
6790
6860
  throw error;
6791
6861
  }
6792
6862
  }
6863
+ async function getInstalledLoadoutFavoriteSkillsStateToken(home = getHome(), env = process.env) {
6864
+ const manifest = await readManifest(home, env);
6865
+ return getLoadoutFavoriteSkillsStateToken(manifest.skills);
6866
+ }
6793
6867
  async function writeManifest(home, manifest) {
6794
6868
  const manifestPath = getManifestPath(home);
6795
6869
  const temporaryPath = `${manifestPath}.${process.pid}.tmp`;
@@ -6865,7 +6939,7 @@ async function reconcileLoadoutFavoriteSkills(payload, home = getHome(), env = p
6865
6939
  continue;
6866
6940
  }
6867
6941
  const allLinksExist = (await Promise.all(linkPaths.map(pathExists))).every(Boolean);
6868
- if (previous?.artifactVersionId === pkg.artifactVersionId && previous.installPath === installPath && previous.linkPaths.length === linkPaths.length && previous.linkPaths.every((linkPath) => linkPaths.includes(linkPath)) && await pathExists(installPath) && allLinksExist) {
6942
+ if (previous?.artifactVersionId === pkg.artifactVersionId && previous.displayName === pkg.displayName && previous.summary === pkg.summary && previous.installPath === installPath && previous.linkPaths.length === linkPaths.length && previous.linkPaths.every((linkPath) => linkPaths.includes(linkPath)) && await pathExists(installPath) && allLinksExist) {
6869
6943
  result.unchanged.push(pkg.name);
6870
6944
  nextSkills.push(previous);
6871
6945
  continue;
@@ -6910,6 +6984,8 @@ async function reconcileLoadoutFavoriteSkills(payload, home = getHome(), env = p
6910
6984
  nextSkills.push({
6911
6985
  name: pkg.name,
6912
6986
  artifactVersionId: pkg.artifactVersionId,
6987
+ displayName: pkg.displayName,
6988
+ summary: pkg.summary,
6913
6989
  commandName: nextCommandName,
6914
6990
  installPath,
6915
6991
  linkPaths
@@ -6962,7 +7038,7 @@ async function genReconcileFavoriteSkills(resolved, result, dependencies) {
6962
7038
  }
6963
7039
 
6964
7040
  // src/localIntegrationMigration.ts
6965
- import { createHash as createHash4 } from "node:crypto";
7041
+ import { createHash as createHash5 } from "node:crypto";
6966
7042
  import { readFile as readFile8 } from "node:fs/promises";
6967
7043
  import { homedir as homedir5 } from "node:os";
6968
7044
  import { basename as basename2, join as join8 } from "node:path";
@@ -7364,7 +7440,7 @@ function withoutMatchText(candidate) {
7364
7440
  };
7365
7441
  }
7366
7442
  function buildCandidateId(candidate) {
7367
- return `local_${createHash4("sha256").update([candidate.localSource, candidate.localLabel, candidate.redactedLocator].join("\x00")).digest("hex").slice(0, 12)}`;
7443
+ return `local_${createHash5("sha256").update([candidate.localSource, candidate.localLabel, candidate.redactedLocator].join("\x00")).digest("hex").slice(0, 12)}`;
7368
7444
  }
7369
7445
  function dedupeRawCandidates(candidates) {
7370
7446
  const byKey = new Map;
@@ -7569,6 +7645,16 @@ function buildCommandArgs(params) {
7569
7645
  continue;
7570
7646
  delete args[reserved];
7571
7647
  }
7648
+ const unsupportedField = Object.keys(args).find((key) => !(key in props) && !(params.subcommand && key === "action"));
7649
+ if (unsupportedField) {
7650
+ const directField = unsupportedField.endsWith("File") ? unsupportedField.slice(0, -"File".length) : undefined;
7651
+ return {
7652
+ error: {
7653
+ code: "invalid-input",
7654
+ message: unsupportedField.includes("-") ? `Use camelCase input fields; --${unsupportedField} is not supported.` : `Unknown input field --${unsupportedField}.${directField && directField in props ? ` Pass --${directField} with the value directly.` : ""}`
7655
+ }
7656
+ };
7657
+ }
7572
7658
  if (positionalJson?.args)
7573
7659
  remaining.pop();
7574
7660
  if (params.subcommand) {
@@ -7710,7 +7796,7 @@ import { homedir as homedir6 } from "node:os";
7710
7796
  import { basename as basename3, dirname as dirname4, isAbsolute as isAbsolute3, join as join9, relative as relative2, resolve as resolve2, sep } from "node:path";
7711
7797
 
7712
7798
  // src/loadoutSkillArtifact.ts
7713
- import { createHash as createHash5 } from "node:crypto";
7799
+ import { createHash as createHash6 } from "node:crypto";
7714
7800
  var MAX_LOADOUT_SKILL_ARTIFACT_BYTES = 512 * 1024;
7715
7801
  async function fetchLoadoutSkillArtifact(descriptorValue, expectedVersion, deps = {}) {
7716
7802
  const descriptor = LoadoutSkillArtifactDescriptorSchema.parse(descriptorValue);
@@ -7735,7 +7821,7 @@ async function fetchLoadoutSkillArtifact(descriptorValue, expectedVersion, deps
7735
7821
  if (bytes.byteLength !== descriptor.byteLength) {
7736
7822
  throw new Error(`Aident skill artifact size mismatch: expected ${descriptor.byteLength}, received ${bytes.byteLength}`);
7737
7823
  }
7738
- const artifactSha256 = createHash5("sha256").update(bytes).digest("hex");
7824
+ const artifactSha256 = createHash6("sha256").update(bytes).digest("hex");
7739
7825
  if (artifactSha256 !== descriptor.sha256)
7740
7826
  throw new Error("Aident skill artifact SHA-256 mismatch");
7741
7827
  let parsed;
@@ -8874,6 +8960,14 @@ async function getLoadoutUpdateWarnings(params, injected = {}) {
8874
8960
  warnings.push(`Aident skill v${policy.skill.recommendedVersion} is recommended. Run \`${command}\` when you are ready.`);
8875
8961
  }
8876
8962
  }
8963
+ if (metadata.favoriteSkills?.syncRequired && await claimNotice({
8964
+ noticeId: "aident-favorite-skills-sync",
8965
+ targetVersion: `0.0.0+favorites.${metadata.favoriteSkills.stateToken}`,
8966
+ homeDir: params.homeDir,
8967
+ intervalMs
8968
+ })) {
8969
+ warnings.push("Your local favorite Skills differ from your Aident favorites. After completing the current task, ask the user whether they want you to run `aident skills sync`.");
8970
+ }
8877
8971
  if (cliNoticeShown || skillNoticeShown) {
8878
8972
  recordNotice(injected.recordNotice, cliNoticeShown && skillNoticeShown ? "cli_and_skill" /* CliAndSkill */ : cliNoticeShown ? "cli" /* Cli */ : "skill" /* Skill */);
8879
8973
  }
@@ -9001,6 +9095,13 @@ function getPreservedShadowBucket(count) {
9001
9095
  return "6+" /* SixOrMore */;
9002
9096
  }
9003
9097
 
9098
+ // ../web/shared/affiliate/AffiliateCode.ts
9099
+ var AFFILIATE_CODE_PATTERN = /^[A-Z0-9]{6,32}$/;
9100
+ function normalizeAffiliateCode(value) {
9101
+ const normalized = value?.trim().toUpperCase();
9102
+ return normalized && AFFILIATE_CODE_PATTERN.test(normalized) ? normalized : null;
9103
+ }
9104
+
9004
9105
  // src/cli.ts
9005
9106
  var LOADOUT_CAPABILITIES_SEARCH_OPERATION = "loadout_capabilities_search";
9006
9107
  var LOADOUT_CAPABILITIES_EXECUTE_OPERATION = "loadout_capabilities_execute";
@@ -9469,7 +9570,12 @@ async function runSetup(parsed) {
9469
9570
  let credentials = await readCredentials();
9470
9571
  const hasEnvironmentToken = !!process.env.AIDENT_TOKEN?.trim();
9471
9572
  if ((!credentials || !credentialsForBaseUrl(credentials, options.baseUrl)) && !hasEnvironmentToken) {
9472
- credentials = await login({ baseUrl: options.baseUrl, format: parsed.format, oob: options.oob });
9573
+ credentials = await login({
9574
+ baseUrl: options.baseUrl,
9575
+ format: parsed.format,
9576
+ oob: options.oob,
9577
+ ref: options.ref
9578
+ });
9473
9579
  await writeCredentials(credentials);
9474
9580
  }
9475
9581
  const client = await getAuthenticatedClient(["loadout"]);
@@ -9515,7 +9621,7 @@ async function runSetup(parsed) {
9515
9621
  async function getSetupOptions(parsed) {
9516
9622
  if (parsed.positional.length !== 1)
9517
9623
  throw new Error("Usage: aident setup [flags]");
9518
- const allowedFlags = new Set(["base-url", "client-name", "oob"]);
9624
+ const allowedFlags = new Set(["base-url", "client-name", "oob", "ref"]);
9519
9625
  const unknownFlag = Object.keys(parsed.flags).find((flag) => !allowedFlags.has(flag));
9520
9626
  if (unknownFlag)
9521
9627
  throw new Error(`Unknown setup flag: --${unknownFlag}`);
@@ -9523,10 +9629,15 @@ async function getSetupOptions(parsed) {
9523
9629
  const clientName = getOptionalStringFlag(parsed.flags, "client-name") ?? "Aident CLI";
9524
9630
  if (clientName.length > 160)
9525
9631
  throw new Error("--client-name must be at most 160 characters");
9632
+ const rawRef = getOptionalStringFlag(parsed.flags, "ref");
9633
+ const ref = normalizeAffiliateCode(rawRef) ?? undefined;
9634
+ if (rawRef && !ref)
9635
+ throw new Error("--ref must be a 6-32 character affiliate code");
9526
9636
  return {
9527
9637
  baseUrl: rawBaseUrl ? normalizeBaseUrl(rawBaseUrl) : await resolveDefaultBaseUrl(),
9528
9638
  clientName,
9529
9639
  oob: getBooleanUpdateFlag(parsed.flags, "oob"),
9640
+ ref,
9530
9641
  persistBaseUrl: !!rawBaseUrl
9531
9642
  };
9532
9643
  }
@@ -9555,7 +9666,7 @@ function renderSetupHelp() {
9555
9666
  `${colors.bold}AIDENT SETUP${colors.reset}`,
9556
9667
  "",
9557
9668
  "USAGE:",
9558
- " aident setup [--client-name <name>] [--base-url <url>] [--oob] [--json]",
9669
+ " aident setup [--client-name <name>] [--base-url <url>] [--ref <code>] [--oob] [--json]",
9559
9670
  "",
9560
9671
  "The setup command updates the CLI, installs or reconciles the verified Aident Skill globally, and opens sign-in when needed.",
9561
9672
  "It verifies Loadout access and records completion. AIDENT_TOKEN enables non-interactive authentication."
@@ -10247,7 +10358,10 @@ async function getRequestedPackages(parsed) {
10247
10358
  }
10248
10359
  async function getAuthenticatedClient(packages) {
10249
10360
  const activePackages = packages ?? await resolveDefaultPackages();
10250
- const skillInventory = await getLoadoutSkillInventory();
10361
+ const [skillInventory, favoriteSkillsStateToken] = await Promise.all([
10362
+ getLoadoutSkillInventory(),
10363
+ getInstalledLoadoutFavoriteSkillsStateToken().catch(() => null)
10364
+ ]);
10251
10365
  const installedSkillVersion = findInstalledLoadoutSkillVersionFromInventory(skillInventory);
10252
10366
  const envToken = process.env.AIDENT_TOKEN;
10253
10367
  const envBaseUrl = process.env.AIDENT_BASE_URL?.trim();
@@ -10257,7 +10371,7 @@ async function getAuthenticatedClient(packages) {
10257
10371
  base_url: normalizeBaseUrl(baseUrl2),
10258
10372
  client_id: "",
10259
10373
  access_token: envToken
10260
- }, "env", activePackages, installedSkillVersion, skillInventory);
10374
+ }, "env", activePackages, installedSkillVersion, skillInventory, favoriteSkillsStateToken);
10261
10375
  }
10262
10376
  let creds = await readCredentials();
10263
10377
  if (!creds)
@@ -10276,7 +10390,7 @@ async function getAuthenticatedClient(packages) {
10276
10390
  creds = refreshed;
10277
10391
  }
10278
10392
  }
10279
- return new CliClient(creds, "stored", activePackages, installedSkillVersion, skillInventory);
10393
+ return new CliClient(creds, "stored", activePackages, installedSkillVersion, skillInventory, favoriteSkillsStateToken);
10280
10394
  }
10281
10395
  var catalogCache = null;
10282
10396
  async function fetchCatalog(client, options = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aident-ai/cli",
3
- "version": "0.1.8",
3
+ "version": "0.2.0-rc.0",
4
4
  "description": "Aident CLI — umbrella access to Loadout integrations, Playbook automation, Intern tools, and the Aident platform.",
5
5
  "homepage": "https://aident.ai",
6
6
  "repository": {