@aident-ai/cli 0.1.8-rc.2 → 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 +139 -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-rc.2";
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,7 +6727,7 @@ 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";
@@ -6737,7 +6801,7 @@ function assertPackage(pkg) {
6737
6801
  packageBytes += bytes;
6738
6802
  if (bytes !== file.sizeBytes)
6739
6803
  throw new Error(`Size mismatch: ${pkg.name}/${file.path}`);
6740
- if (createHash3("sha256").update(file.content).digest("hex") !== file.contentDigest) {
6804
+ if (createHash4("sha256").update(file.content).digest("hex") !== file.contentDigest) {
6741
6805
  throw new Error(`Digest mismatch: ${pkg.name}/${file.path}`);
6742
6806
  }
6743
6807
  }
@@ -6760,7 +6824,7 @@ function entrypointContent(pkg, content) {
6760
6824
  return [
6761
6825
  "---",
6762
6826
  `name: ${quoteYaml(commandName(pkg))}`,
6763
- `description: ${quoteYaml(pkg.summary)}`,
6827
+ `description: ${quoteYaml(`${pkg.displayName}: ${pkg.summary}`)}`,
6764
6828
  "---",
6765
6829
  `<!-- aident-loadout-skill name=${pkg.name} artifact-version=${pkg.artifactVersionId} -->`,
6766
6830
  "",
@@ -6785,7 +6849,7 @@ async function readManifest(home, env) {
6785
6849
  throw new Error("Unsupported manifest");
6786
6850
  const harnessRoots = new Set(getHarnessHosts(home, env).map(({ root }) => path.join(root, "skills")));
6787
6851
  for (const skill of value.skills) {
6788
- 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)) {
6789
6853
  throw new Error("Invalid favorite Skills ownership manifest");
6790
6854
  }
6791
6855
  }
@@ -6796,6 +6860,10 @@ async function readManifest(home, env) {
6796
6860
  throw error;
6797
6861
  }
6798
6862
  }
6863
+ async function getInstalledLoadoutFavoriteSkillsStateToken(home = getHome(), env = process.env) {
6864
+ const manifest = await readManifest(home, env);
6865
+ return getLoadoutFavoriteSkillsStateToken(manifest.skills);
6866
+ }
6799
6867
  async function writeManifest(home, manifest) {
6800
6868
  const manifestPath = getManifestPath(home);
6801
6869
  const temporaryPath = `${manifestPath}.${process.pid}.tmp`;
@@ -6871,7 +6939,7 @@ async function reconcileLoadoutFavoriteSkills(payload, home = getHome(), env = p
6871
6939
  continue;
6872
6940
  }
6873
6941
  const allLinksExist = (await Promise.all(linkPaths.map(pathExists))).every(Boolean);
6874
- 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) {
6875
6943
  result.unchanged.push(pkg.name);
6876
6944
  nextSkills.push(previous);
6877
6945
  continue;
@@ -6916,6 +6984,8 @@ async function reconcileLoadoutFavoriteSkills(payload, home = getHome(), env = p
6916
6984
  nextSkills.push({
6917
6985
  name: pkg.name,
6918
6986
  artifactVersionId: pkg.artifactVersionId,
6987
+ displayName: pkg.displayName,
6988
+ summary: pkg.summary,
6919
6989
  commandName: nextCommandName,
6920
6990
  installPath,
6921
6991
  linkPaths
@@ -6968,7 +7038,7 @@ async function genReconcileFavoriteSkills(resolved, result, dependencies) {
6968
7038
  }
6969
7039
 
6970
7040
  // src/localIntegrationMigration.ts
6971
- import { createHash as createHash4 } from "node:crypto";
7041
+ import { createHash as createHash5 } from "node:crypto";
6972
7042
  import { readFile as readFile8 } from "node:fs/promises";
6973
7043
  import { homedir as homedir5 } from "node:os";
6974
7044
  import { basename as basename2, join as join8 } from "node:path";
@@ -7370,7 +7440,7 @@ function withoutMatchText(candidate) {
7370
7440
  };
7371
7441
  }
7372
7442
  function buildCandidateId(candidate) {
7373
- 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)}`;
7374
7444
  }
7375
7445
  function dedupeRawCandidates(candidates) {
7376
7446
  const byKey = new Map;
@@ -7575,6 +7645,16 @@ function buildCommandArgs(params) {
7575
7645
  continue;
7576
7646
  delete args[reserved];
7577
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
+ }
7578
7658
  if (positionalJson?.args)
7579
7659
  remaining.pop();
7580
7660
  if (params.subcommand) {
@@ -7716,7 +7796,7 @@ import { homedir as homedir6 } from "node:os";
7716
7796
  import { basename as basename3, dirname as dirname4, isAbsolute as isAbsolute3, join as join9, relative as relative2, resolve as resolve2, sep } from "node:path";
7717
7797
 
7718
7798
  // src/loadoutSkillArtifact.ts
7719
- import { createHash as createHash5 } from "node:crypto";
7799
+ import { createHash as createHash6 } from "node:crypto";
7720
7800
  var MAX_LOADOUT_SKILL_ARTIFACT_BYTES = 512 * 1024;
7721
7801
  async function fetchLoadoutSkillArtifact(descriptorValue, expectedVersion, deps = {}) {
7722
7802
  const descriptor = LoadoutSkillArtifactDescriptorSchema.parse(descriptorValue);
@@ -7741,7 +7821,7 @@ async function fetchLoadoutSkillArtifact(descriptorValue, expectedVersion, deps
7741
7821
  if (bytes.byteLength !== descriptor.byteLength) {
7742
7822
  throw new Error(`Aident skill artifact size mismatch: expected ${descriptor.byteLength}, received ${bytes.byteLength}`);
7743
7823
  }
7744
- const artifactSha256 = createHash5("sha256").update(bytes).digest("hex");
7824
+ const artifactSha256 = createHash6("sha256").update(bytes).digest("hex");
7745
7825
  if (artifactSha256 !== descriptor.sha256)
7746
7826
  throw new Error("Aident skill artifact SHA-256 mismatch");
7747
7827
  let parsed;
@@ -8880,6 +8960,14 @@ async function getLoadoutUpdateWarnings(params, injected = {}) {
8880
8960
  warnings.push(`Aident skill v${policy.skill.recommendedVersion} is recommended. Run \`${command}\` when you are ready.`);
8881
8961
  }
8882
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
+ }
8883
8971
  if (cliNoticeShown || skillNoticeShown) {
8884
8972
  recordNotice(injected.recordNotice, cliNoticeShown && skillNoticeShown ? "cli_and_skill" /* CliAndSkill */ : cliNoticeShown ? "cli" /* Cli */ : "skill" /* Skill */);
8885
8973
  }
@@ -9007,6 +9095,13 @@ function getPreservedShadowBucket(count) {
9007
9095
  return "6+" /* SixOrMore */;
9008
9096
  }
9009
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
+
9010
9105
  // src/cli.ts
9011
9106
  var LOADOUT_CAPABILITIES_SEARCH_OPERATION = "loadout_capabilities_search";
9012
9107
  var LOADOUT_CAPABILITIES_EXECUTE_OPERATION = "loadout_capabilities_execute";
@@ -9475,7 +9570,12 @@ async function runSetup(parsed) {
9475
9570
  let credentials = await readCredentials();
9476
9571
  const hasEnvironmentToken = !!process.env.AIDENT_TOKEN?.trim();
9477
9572
  if ((!credentials || !credentialsForBaseUrl(credentials, options.baseUrl)) && !hasEnvironmentToken) {
9478
- 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
+ });
9479
9579
  await writeCredentials(credentials);
9480
9580
  }
9481
9581
  const client = await getAuthenticatedClient(["loadout"]);
@@ -9521,7 +9621,7 @@ async function runSetup(parsed) {
9521
9621
  async function getSetupOptions(parsed) {
9522
9622
  if (parsed.positional.length !== 1)
9523
9623
  throw new Error("Usage: aident setup [flags]");
9524
- const allowedFlags = new Set(["base-url", "client-name", "oob"]);
9624
+ const allowedFlags = new Set(["base-url", "client-name", "oob", "ref"]);
9525
9625
  const unknownFlag = Object.keys(parsed.flags).find((flag) => !allowedFlags.has(flag));
9526
9626
  if (unknownFlag)
9527
9627
  throw new Error(`Unknown setup flag: --${unknownFlag}`);
@@ -9529,10 +9629,15 @@ async function getSetupOptions(parsed) {
9529
9629
  const clientName = getOptionalStringFlag(parsed.flags, "client-name") ?? "Aident CLI";
9530
9630
  if (clientName.length > 160)
9531
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");
9532
9636
  return {
9533
9637
  baseUrl: rawBaseUrl ? normalizeBaseUrl(rawBaseUrl) : await resolveDefaultBaseUrl(),
9534
9638
  clientName,
9535
9639
  oob: getBooleanUpdateFlag(parsed.flags, "oob"),
9640
+ ref,
9536
9641
  persistBaseUrl: !!rawBaseUrl
9537
9642
  };
9538
9643
  }
@@ -9561,7 +9666,7 @@ function renderSetupHelp() {
9561
9666
  `${colors.bold}AIDENT SETUP${colors.reset}`,
9562
9667
  "",
9563
9668
  "USAGE:",
9564
- " aident setup [--client-name <name>] [--base-url <url>] [--oob] [--json]",
9669
+ " aident setup [--client-name <name>] [--base-url <url>] [--ref <code>] [--oob] [--json]",
9565
9670
  "",
9566
9671
  "The setup command updates the CLI, installs or reconciles the verified Aident Skill globally, and opens sign-in when needed.",
9567
9672
  "It verifies Loadout access and records completion. AIDENT_TOKEN enables non-interactive authentication."
@@ -10253,7 +10358,10 @@ async function getRequestedPackages(parsed) {
10253
10358
  }
10254
10359
  async function getAuthenticatedClient(packages) {
10255
10360
  const activePackages = packages ?? await resolveDefaultPackages();
10256
- const skillInventory = await getLoadoutSkillInventory();
10361
+ const [skillInventory, favoriteSkillsStateToken] = await Promise.all([
10362
+ getLoadoutSkillInventory(),
10363
+ getInstalledLoadoutFavoriteSkillsStateToken().catch(() => null)
10364
+ ]);
10257
10365
  const installedSkillVersion = findInstalledLoadoutSkillVersionFromInventory(skillInventory);
10258
10366
  const envToken = process.env.AIDENT_TOKEN;
10259
10367
  const envBaseUrl = process.env.AIDENT_BASE_URL?.trim();
@@ -10263,7 +10371,7 @@ async function getAuthenticatedClient(packages) {
10263
10371
  base_url: normalizeBaseUrl(baseUrl2),
10264
10372
  client_id: "",
10265
10373
  access_token: envToken
10266
- }, "env", activePackages, installedSkillVersion, skillInventory);
10374
+ }, "env", activePackages, installedSkillVersion, skillInventory, favoriteSkillsStateToken);
10267
10375
  }
10268
10376
  let creds = await readCredentials();
10269
10377
  if (!creds)
@@ -10282,7 +10390,7 @@ async function getAuthenticatedClient(packages) {
10282
10390
  creds = refreshed;
10283
10391
  }
10284
10392
  }
10285
- return new CliClient(creds, "stored", activePackages, installedSkillVersion, skillInventory);
10393
+ return new CliClient(creds, "stored", activePackages, installedSkillVersion, skillInventory, favoriteSkillsStateToken);
10286
10394
  }
10287
10395
  var catalogCache = null;
10288
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-rc.2",
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": {