@usesocial/cli 0.11.6 → 0.11.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from "node:module";
3
3
  import * as fs from "node:fs";
4
- import { createReadStream, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
4
+ import { createReadStream, existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
5
+ import { basename, dirname, extname, join, resolve } from "node:path";
5
6
  import { fileURLToPath } from "node:url";
6
7
  import { homedir } from "node:os";
7
- import { basename, dirname, extname, join, resolve } from "node:path";
8
8
  import { parseArgs, styleText } from "node:util";
9
- import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
9
+ import { chmod, lstat, mkdir, readFile, readdir, readlink, rm, writeFile } from "node:fs/promises";
10
10
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
11
11
  import { createHash } from "node:crypto";
12
12
  import { dirname as dirname$1, isAbsolute, posix, relative, sep } from "path";
@@ -15,6 +15,7 @@ import l__default, { createInterface } from "node:readline";
15
15
  import { AsyncLocalStorage } from "node:async_hooks";
16
16
  import process$1, { stdin, stdout } from "node:process";
17
17
  import { ReadStream } from "node:tty";
18
+ import { spawn, spawnSync } from "node:child_process";
18
19
  //#region \0rolldown/runtime.js
19
20
  var __create = Object.create;
20
21
  var __defProp = Object.defineProperty;
@@ -11039,6 +11040,7 @@ pgTable("cli_session_grants", {
11039
11040
  }, (table) => [index("cli_session_grants_user_idx").on(table.userId, table.createdAt.desc()), check("cli_session_grants_scope_alias_check", sqlCheckIn(table.scopeAlias, CLI_SCOPE_ALIASES$1))]);
11040
11041
  pgTable("wrapped", {
11041
11042
  id: text$1("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
11043
+ userId: text$1("user_id").references(() => user.id, { onDelete: "cascade" }),
11042
11044
  platform: text$1("platform", { enum: PLATFORMS }).notNull().default("x"),
11043
11045
  username: text$1("username").notNull(),
11044
11046
  platformProfileId: text$1("platform_profile_id").notNull(),
@@ -11052,6 +11054,7 @@ pgTable("wrapped", {
11052
11054
  updatedAt: timestamptz("updated_at").notNull().defaultNow()
11053
11055
  }, (table) => [
11054
11056
  uniqueIndex("wrapped_platform_profile_key").on(table.platform, table.platformProfileId),
11057
+ index("wrapped_user_platform_idx").on(table.userId, table.platform, table.updatedAt.desc()).where(sql`${table.userId} is not null`),
11055
11058
  index("wrapped_platform_username_idx").on(table.platform, table.username),
11056
11059
  check("wrapped_platform_check", sqlCheckIn(table.platform, PLATFORMS)),
11057
11060
  check("wrapped_status_check", sqlCheckIn(table.status, WRAPPED_STATUSES))
@@ -23172,7 +23175,7 @@ function createEnv(opts) {
23172
23175
  }
23173
23176
  //#endregion
23174
23177
  //#region package.json
23175
- var version$1 = "0.11.6";
23178
+ var version$1 = "0.11.8";
23176
23179
  //#endregion
23177
23180
  //#region src/lib/env.ts
23178
23181
  const URLWithTrailingSlash = url().transform(ensureTrailingSlash);
@@ -23682,6 +23685,9 @@ const readStdin = async () => {
23682
23685
  const printLine = (value) => {
23683
23686
  outputWriters.stdout(value);
23684
23687
  };
23688
+ const printStderrLine = (value) => {
23689
+ outputWriters.stderr(value);
23690
+ };
23685
23691
  const printData = (value) => {
23686
23692
  outputWriters.stdout(JSON.stringify(value));
23687
23693
  };
@@ -27121,24 +27127,24 @@ function matchProperty(property, propertyValues, warnFunction) {
27121
27127
  if (["is_date_before"].includes(operator)) return overrideDate < parsedDate;
27122
27128
  return overrideDate > parsedDate;
27123
27129
  }
27124
- case "semver_eq": return 0 === compareSemverTuples(parseSemver(String(overrideValue)), parseSemver(String(value)));
27125
- case "semver_neq": return 0 !== compareSemverTuples(parseSemver(String(overrideValue)), parseSemver(String(value)));
27126
- case "semver_gt": return compareSemverTuples(parseSemver(String(overrideValue)), parseSemver(String(value))) > 0;
27127
- case "semver_gte": return compareSemverTuples(parseSemver(String(overrideValue)), parseSemver(String(value))) >= 0;
27128
- case "semver_lt": return compareSemverTuples(parseSemver(String(overrideValue)), parseSemver(String(value))) < 0;
27129
- case "semver_lte": return compareSemverTuples(parseSemver(String(overrideValue)), parseSemver(String(value))) <= 0;
27130
+ case "semver_eq": return 0 === compareSemverTuples(parseSemver$1(String(overrideValue)), parseSemver$1(String(value)));
27131
+ case "semver_neq": return 0 !== compareSemverTuples(parseSemver$1(String(overrideValue)), parseSemver$1(String(value)));
27132
+ case "semver_gt": return compareSemverTuples(parseSemver$1(String(overrideValue)), parseSemver$1(String(value))) > 0;
27133
+ case "semver_gte": return compareSemverTuples(parseSemver$1(String(overrideValue)), parseSemver$1(String(value))) >= 0;
27134
+ case "semver_lt": return compareSemverTuples(parseSemver$1(String(overrideValue)), parseSemver$1(String(value))) < 0;
27135
+ case "semver_lte": return compareSemverTuples(parseSemver$1(String(overrideValue)), parseSemver$1(String(value))) <= 0;
27130
27136
  case "semver_tilde": {
27131
- const overrideParsed = parseSemver(String(overrideValue));
27137
+ const overrideParsed = parseSemver$1(String(overrideValue));
27132
27138
  const { lower, upper } = computeTildeBounds(String(value));
27133
27139
  return compareSemverTuples(overrideParsed, lower) >= 0 && compareSemverTuples(overrideParsed, upper) < 0;
27134
27140
  }
27135
27141
  case "semver_caret": {
27136
- const overrideParsed = parseSemver(String(overrideValue));
27142
+ const overrideParsed = parseSemver$1(String(overrideValue));
27137
27143
  const { lower, upper } = computeCaretBounds(String(value));
27138
27144
  return compareSemverTuples(overrideParsed, lower) >= 0 && compareSemverTuples(overrideParsed, upper) < 0;
27139
27145
  }
27140
27146
  case "semver_wildcard": {
27141
- const overrideParsed = parseSemver(String(overrideValue));
27147
+ const overrideParsed = parseSemver$1(String(overrideValue));
27142
27148
  const { lower, upper } = computeWildcardBounds(String(value));
27143
27149
  return compareSemverTuples(overrideParsed, lower) >= 0 && compareSemverTuples(overrideParsed, upper) < 0;
27144
27150
  }
@@ -27214,7 +27220,7 @@ function parseSemverNumericIdentifier(part, raw) {
27214
27220
  if (part.length > 1 && "0" === part[0]) throw new InconclusiveMatchError(`Invalid semver: ${raw}`);
27215
27221
  return parseInt(part, 10);
27216
27222
  }
27217
- function parseSemver(value) {
27223
+ function parseSemver$1(value) {
27218
27224
  const baseVersion = String(value).trim().replace(/^[vV]/, "").split("-")[0].split("+")[0];
27219
27225
  if (!baseVersion || baseVersion.startsWith(".")) throw new InconclusiveMatchError(`Invalid semver: ${value}`);
27220
27226
  const parts = baseVersion.split(".");
@@ -27236,7 +27242,7 @@ function compareSemverTuples(a, b) {
27236
27242
  return 0;
27237
27243
  }
27238
27244
  function computeTildeBounds(value) {
27239
- const parsed = parseSemver(value);
27245
+ const parsed = parseSemver$1(value);
27240
27246
  return {
27241
27247
  lower: [
27242
27248
  parsed[0],
@@ -27251,7 +27257,7 @@ function computeTildeBounds(value) {
27251
27257
  };
27252
27258
  }
27253
27259
  function computeCaretBounds(value) {
27254
- const [major, minor, patch] = parseSemver(value);
27260
+ const [major, minor, patch] = parseSemver$1(value);
27255
27261
  const lower = [
27256
27262
  major,
27257
27263
  minor,
@@ -28471,7 +28477,7 @@ const outcomeForExitCode = (code) => {
28471
28477
  }
28472
28478
  };
28473
28479
  /** Any non-empty value other than "0"/"false" counts as truthy. */
28474
- const truthy = (value) => {
28480
+ const truthy$1 = (value) => {
28475
28481
  if (!value) return false;
28476
28482
  const normalized = value.trim().toLowerCase();
28477
28483
  return normalized !== "" && normalized !== "0" && normalized !== "false";
@@ -28480,7 +28486,7 @@ const truthy = (value) => {
28480
28486
  * `enabled` is pure env — computed once, no I/O. When false, no client is
28481
28487
  * constructed, the state file is never touched, and no notice is printed.
28482
28488
  */
28483
- const computeEnabled = () => !!env.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN && !truthy(env.DO_NOT_TRACK) && !truthy(env.SOCIAL_DO_NOT_TRACK) && !truthy(env.CI);
28489
+ const computeEnabled = () => !!env.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN && !truthy$1(env.DO_NOT_TRACK) && !truthy$1(env.SOCIAL_DO_NOT_TRACK) && !truthy$1(env.CI);
28484
28490
  const statePath = () => join(homedir(), ".social", "analytics.json");
28485
28491
  /**
28486
28492
  * Read existing state, or create a fresh anonymousId on first run. Returns
@@ -37161,6 +37167,516 @@ const schemaCommand = defineCommand({
37161
37167
  }
37162
37168
  });
37163
37169
  //#endregion
37170
+ //#region src/update/check.ts
37171
+ const NPM_REGISTRY_URL = "https://registry.npmjs.org/@usesocial%2fcli";
37172
+ const SKILL_TREE_URL = "https://api.github.com/repos/usesocial/skill/git/trees/main:skills/social";
37173
+ const UPDATE_CACHE_TTL_MS = 1200 * 60 * 1e3;
37174
+ const UPDATE_NOTICE_TTL_MS = 1440 * 60 * 1e3;
37175
+ const INTERNAL_UPDATE_PROBE_ENV = "SOCIAL_INTERNAL_UPDATE_PROBE";
37176
+ _enum([
37177
+ "brew",
37178
+ "bun",
37179
+ "npm"
37180
+ ]);
37181
+ const CLIUpdateStatus = _enum([
37182
+ "current",
37183
+ "update_available",
37184
+ "unknown"
37185
+ ]);
37186
+ const SkillUpdateStatus = _enum([
37187
+ "current",
37188
+ "not_installed",
37189
+ "update_available",
37190
+ "unknown"
37191
+ ]);
37192
+ const SkillScope = _enum(["project", "global"]);
37193
+ const NPMRegistryResponse = object({
37194
+ "dist-tags": object({ latest: string().optional() }).passthrough().optional(),
37195
+ versions: record(string(), object({ dist: object({
37196
+ integrity: string().optional(),
37197
+ tarball: string().optional()
37198
+ }).passthrough().optional() }).passthrough())
37199
+ }).passthrough();
37200
+ const GitHubTreeResponse = object({ sha: string() });
37201
+ const CacheFile = object({
37202
+ checkedAt: number().int().nonnegative(),
37203
+ cli: object({
37204
+ currentVersion: string(),
37205
+ latestVersion: string().nullable(),
37206
+ status: CLIUpdateStatus,
37207
+ updateCommand: string().nullable()
37208
+ }),
37209
+ notices: record(string(), number().int().nonnegative()).optional(),
37210
+ skill: object({
37211
+ localTreeSHA: string().optional(),
37212
+ remoteTreeSHA: string().optional(),
37213
+ scope: SkillScope.nullable(),
37214
+ status: SkillUpdateStatus,
37215
+ updateCommand: string().nullable()
37216
+ })
37217
+ }).strict();
37218
+ const internalPackageRoot = () => {
37219
+ const dir = dirname(fileURLToPath(import.meta.url));
37220
+ const leaf = basename(dir);
37221
+ return leaf === "dist" || leaf === "src" ? dirname(dir) : dirname(dirname(dir));
37222
+ };
37223
+ const updateCachePath = (home = process.env.HOME ?? homedir()) => join(home, ".social", "update-check.json");
37224
+ const defaultCommandRunner = (cmd, args, options) => {
37225
+ const result = spawnSync(cmd, args, {
37226
+ cwd: options?.cwd,
37227
+ encoding: "utf8",
37228
+ maxBuffer: 1024 * 1024,
37229
+ stdio: [
37230
+ "ignore",
37231
+ "pipe",
37232
+ "ignore"
37233
+ ],
37234
+ timeout: 1e4
37235
+ });
37236
+ return {
37237
+ ok: result.status === 0,
37238
+ stdout: typeof result.stdout === "string" ? result.stdout : ""
37239
+ };
37240
+ };
37241
+ const truthy = (value) => {
37242
+ if (!value) return false;
37243
+ const normalized = value.trim().toLowerCase();
37244
+ return normalized !== "" && normalized !== "0" && normalized !== "false";
37245
+ };
37246
+ const updateAwarenessEnabled = () => process.stderr.isTTY && !truthy(process.env.CI) && !truthy(process.env.DO_NOT_TRACK) && !truthy(process.env.SOCIAL_DO_NOT_TRACK);
37247
+ const parseSemver = (version) => {
37248
+ const match = /^v?(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)(?:-(?<pre>[0-9A-Za-z.-]+))?/.exec(version);
37249
+ if (!match?.groups) return;
37250
+ return {
37251
+ major: Number(match.groups.major),
37252
+ minor: Number(match.groups.minor),
37253
+ patch: Number(match.groups.patch),
37254
+ prerelease: match.groups.pre?.split(".") ?? []
37255
+ };
37256
+ };
37257
+ const compareIdentifier = (left, right) => {
37258
+ const leftNumber = /^\d+$/.test(left) ? Number(left) : void 0;
37259
+ const rightNumber = /^\d+$/.test(right) ? Number(right) : void 0;
37260
+ if (leftNumber !== void 0 && rightNumber !== void 0) return Math.sign(leftNumber - rightNumber);
37261
+ if (leftNumber !== void 0) return -1;
37262
+ if (rightNumber !== void 0) return 1;
37263
+ return left.localeCompare(right);
37264
+ };
37265
+ const compareSemver = (left, right) => {
37266
+ const parsedLeft = parseSemver(left);
37267
+ const parsedRight = parseSemver(right);
37268
+ if (!(parsedLeft && parsedRight)) return left.localeCompare(right);
37269
+ for (const key of [
37270
+ "major",
37271
+ "minor",
37272
+ "patch"
37273
+ ]) {
37274
+ const delta = parsedLeft[key] - parsedRight[key];
37275
+ if (delta !== 0) return Math.sign(delta);
37276
+ }
37277
+ if (parsedLeft.prerelease.length === 0 && parsedRight.prerelease.length > 0) return 1;
37278
+ if (parsedLeft.prerelease.length > 0 && parsedRight.prerelease.length === 0) return -1;
37279
+ const length = Math.max(parsedLeft.prerelease.length, parsedRight.prerelease.length);
37280
+ for (let index = 0; index < length; index += 1) {
37281
+ const leftPart = parsedLeft.prerelease[index];
37282
+ const rightPart = parsedRight.prerelease[index];
37283
+ if (leftPart === void 0) return -1;
37284
+ if (rightPart === void 0) return 1;
37285
+ const delta = compareIdentifier(leftPart, rightPart);
37286
+ if (delta !== 0) return delta;
37287
+ }
37288
+ return 0;
37289
+ };
37290
+ const detectPackageManager = (packageRoot) => {
37291
+ const normalized = packageRoot.replaceAll("\\", "/");
37292
+ if (normalized.includes("/Cellar/usesocial/") || normalized.includes("/Cellar/cli/") || normalized.includes("/Homebrew/")) return "brew";
37293
+ if (normalized.includes("/.bun/install/global/")) return "bun";
37294
+ return "npm";
37295
+ };
37296
+ const cliUpdateCommand = (manager) => {
37297
+ if (manager === "brew") return "brew upgrade usesocial/tap/cli";
37298
+ if (manager === "bun") return "bun install -g @usesocial/cli@latest";
37299
+ return "npm install -g @usesocial/cli@latest";
37300
+ };
37301
+ const skillUpdateRunner = (manager) => manager === "bun" ? "bunx --bun" : "npx --yes";
37302
+ const skillUpdateCommand = (scope, manager) => `${skillUpdateRunner(manager)} skills update social --${scope} --yes`;
37303
+ const parseNPMUpdateResult = (value, currentVersion, manager) => {
37304
+ const parsed = NPMRegistryResponse.safeParse(value);
37305
+ if (!parsed.success) return {
37306
+ currentVersion,
37307
+ latestVersion: null,
37308
+ status: "unknown",
37309
+ updateCommand: null
37310
+ };
37311
+ const latestVersion = parsed.data["dist-tags"]?.latest ?? null;
37312
+ if (!latestVersion || compareSemver(latestVersion, currentVersion) <= 0) return {
37313
+ currentVersion,
37314
+ latestVersion,
37315
+ status: "current",
37316
+ updateCommand: null
37317
+ };
37318
+ const latestPackage = parsed.data.versions[latestVersion];
37319
+ if (!(latestPackage?.dist?.tarball && latestPackage.dist.integrity)) return {
37320
+ currentVersion,
37321
+ latestVersion,
37322
+ status: "unknown",
37323
+ updateCommand: null
37324
+ };
37325
+ return {
37326
+ currentVersion,
37327
+ latestVersion,
37328
+ status: "update_available",
37329
+ updateCommand: cliUpdateCommand(manager)
37330
+ };
37331
+ };
37332
+ const unknownCLIStatus = () => ({
37333
+ currentVersion: VERSION,
37334
+ latestVersion: null,
37335
+ status: "unknown",
37336
+ updateCommand: null
37337
+ });
37338
+ const checkCLIUpdate = async (deps = {}) => {
37339
+ const packageRoot = deps.packageRoot ?? internalPackageRoot();
37340
+ const fetchImpl = deps.fetch ?? fetch;
37341
+ try {
37342
+ const response = await fetchImpl(NPM_REGISTRY_URL, { headers: { accept: "application/json" } });
37343
+ if (!response.ok) return unknownCLIStatus();
37344
+ return parseNPMUpdateResult(await response.json(), VERSION, detectPackageManager(packageRoot));
37345
+ } catch {
37346
+ return unknownCLIStatus();
37347
+ }
37348
+ };
37349
+ const readJSON = async (path) => {
37350
+ try {
37351
+ return JSON.parse(await readFile(path, "utf8"));
37352
+ } catch {
37353
+ return;
37354
+ }
37355
+ };
37356
+ const lockSkillFrom = (value) => {
37357
+ if (!value || typeof value !== "object") return;
37358
+ const skills = value.skills;
37359
+ if (!skills || typeof skills !== "object") return;
37360
+ const social = skills.social;
37361
+ if (!social || typeof social !== "object") return;
37362
+ const record = social;
37363
+ return {
37364
+ source: typeof record.source === "string" ? record.source : void 0,
37365
+ skillPath: typeof record.skillPath === "string" ? record.skillPath : void 0
37366
+ };
37367
+ };
37368
+ const skillFolderFromLock = (lockPath, skill) => {
37369
+ if (!skill?.skillPath) return;
37370
+ return join(dirname(lockPath), dirname(skill.skillPath));
37371
+ };
37372
+ const candidateFromLock = async (lockPath, scope) => {
37373
+ const skill = lockSkillFrom(await readJSON(lockPath));
37374
+ const folder = skillFolderFromLock(lockPath, skill);
37375
+ if (!folder) return;
37376
+ return {
37377
+ folder,
37378
+ scope,
37379
+ source: skill?.source
37380
+ };
37381
+ };
37382
+ const folderFromListEntry = (entry) => {
37383
+ if (!entry || typeof entry !== "object") return;
37384
+ const record = entry;
37385
+ for (const key of [
37386
+ "folder",
37387
+ "path",
37388
+ "directory",
37389
+ "dir"
37390
+ ]) if (typeof record[key] === "string") return record[key];
37391
+ if (typeof record.skillPath === "string") return dirname(record.skillPath);
37392
+ };
37393
+ const listEntriesFrom = (value) => {
37394
+ if (Array.isArray(value)) return value;
37395
+ if (!value || typeof value !== "object") return [];
37396
+ const record = value;
37397
+ if (Array.isArray(record.skills)) return record.skills;
37398
+ if (record.skills && typeof record.skills === "object") return Object.entries(record.skills).map(([name, skill]) => skill && typeof skill === "object" ? {
37399
+ name,
37400
+ ...skill
37401
+ } : { name });
37402
+ return [];
37403
+ };
37404
+ const candidateFromSkillsList = (rawJSON, lockPath, scope) => {
37405
+ try {
37406
+ const folder = folderFromListEntry(listEntriesFrom(JSON.parse(rawJSON)).find((entry) => {
37407
+ if (!entry || typeof entry !== "object") return false;
37408
+ const record = entry;
37409
+ return record.name === "social" || record.id === "social";
37410
+ }));
37411
+ if (!folder) return;
37412
+ return {
37413
+ folder: folder.startsWith("/") ? folder : join(dirname(lockPath), folder),
37414
+ scope
37415
+ };
37416
+ } catch {
37417
+ return;
37418
+ }
37419
+ };
37420
+ const installedSkillCandidate = async (deps) => {
37421
+ const cwd = deps.cwd ?? process.cwd();
37422
+ const home = deps.home ?? process.env.HOME ?? homedir();
37423
+ const runCommand = deps.runCommand ?? defaultCommandRunner;
37424
+ const candidates = [{
37425
+ args: ["list", "--json"],
37426
+ lockPath: join(cwd, "skills-lock.json"),
37427
+ scope: "project"
37428
+ }, {
37429
+ args: [
37430
+ "list",
37431
+ "--json",
37432
+ "--global"
37433
+ ],
37434
+ lockPath: join(home, ".agents", ".skill-lock.json"),
37435
+ scope: "global"
37436
+ }];
37437
+ for (const candidate of candidates) {
37438
+ const fromLock = await candidateFromLock(candidate.lockPath, candidate.scope);
37439
+ if (fromLock?.source === "usesocial/skill" && existsSync(fromLock.folder)) return fromLock;
37440
+ const listed = runCommand("skills", candidate.args, { cwd });
37441
+ if (!listed.ok) continue;
37442
+ const fromList = candidateFromSkillsList(listed.stdout, candidate.lockPath, candidate.scope);
37443
+ if (!fromList) continue;
37444
+ const lock = lockSkillFrom(await readJSON(candidate.lockPath));
37445
+ if (lock?.source !== "usesocial/skill") continue;
37446
+ return {
37447
+ ...fromList,
37448
+ source: lock.source
37449
+ };
37450
+ }
37451
+ };
37452
+ const hashGitObject = (type, body) => createHash("sha1").update(Buffer.from(`${type} ${body.length}\0`)).update(body).digest("hex");
37453
+ const gitBlobHash = (content) => hashGitObject("blob", content);
37454
+ const compareTreeEntry = (left, right) => Buffer.compare(Buffer.from(left.sortName), Buffer.from(right.sortName));
37455
+ const gitTreeHashForFolder = async (folder) => {
37456
+ const dirents = await readdir(folder, { withFileTypes: true });
37457
+ const entries = [];
37458
+ for (const dirent of dirents) {
37459
+ if (dirent.name === ".git") continue;
37460
+ const path = join(folder, dirent.name);
37461
+ const stat = await lstat(path);
37462
+ if (stat.isDirectory()) {
37463
+ entries.push({
37464
+ mode: "40000",
37465
+ name: dirent.name,
37466
+ sha: await gitTreeHashForFolder(path),
37467
+ sortName: `${dirent.name}/`
37468
+ });
37469
+ continue;
37470
+ }
37471
+ if (stat.isSymbolicLink()) {
37472
+ entries.push({
37473
+ mode: "120000",
37474
+ name: dirent.name,
37475
+ sha: gitBlobHash(Buffer.from(await readlink(path))),
37476
+ sortName: dirent.name
37477
+ });
37478
+ continue;
37479
+ }
37480
+ if (stat.isFile()) {
37481
+ const executable = [
37482
+ 1,
37483
+ 8,
37484
+ 64
37485
+ ].some((bit) => Math.floor(stat.mode / bit) % 2 === 1);
37486
+ entries.push({
37487
+ mode: executable ? "100755" : "100644",
37488
+ name: dirent.name,
37489
+ sha: gitBlobHash(await readFile(path)),
37490
+ sortName: dirent.name
37491
+ });
37492
+ }
37493
+ }
37494
+ return hashGitObject("tree", Buffer.concat(entries.sort(compareTreeEntry).map((entry) => Buffer.concat([Buffer.from(`${entry.mode} ${entry.name}\0`), Buffer.from(entry.sha, "hex")]))));
37495
+ };
37496
+ const remoteSkillTreeSHA = async (fetchImpl) => {
37497
+ const response = await fetchImpl(SKILL_TREE_URL, { headers: { accept: "application/vnd.github+json" } });
37498
+ if (!response.ok) return;
37499
+ const parsed = GitHubTreeResponse.safeParse(await response.json());
37500
+ return parsed.success ? parsed.data.sha : void 0;
37501
+ };
37502
+ const unknownSkillStatus = () => ({
37503
+ scope: null,
37504
+ status: "unknown",
37505
+ updateCommand: null
37506
+ });
37507
+ const checkSkillUpdate = async (deps = {}) => {
37508
+ const manager = detectPackageManager(deps.packageRoot ?? internalPackageRoot());
37509
+ const install = await installedSkillCandidate(deps);
37510
+ if (!install) return {
37511
+ scope: null,
37512
+ status: "not_installed",
37513
+ updateCommand: null
37514
+ };
37515
+ if (install.source !== "usesocial/skill") return unknownSkillStatus();
37516
+ try {
37517
+ const fetchImpl = deps.fetch ?? fetch;
37518
+ const [localSHA, remoteSHA] = await Promise.all([gitTreeHashForFolder(install.folder), remoteSkillTreeSHA(fetchImpl)]);
37519
+ if (!remoteSHA) return {
37520
+ scope: install.scope,
37521
+ status: "unknown",
37522
+ updateCommand: null
37523
+ };
37524
+ return {
37525
+ localTreeSHA: localSHA,
37526
+ remoteTreeSHA: remoteSHA,
37527
+ scope: install.scope,
37528
+ status: localSHA === remoteSHA ? "current" : "update_available",
37529
+ updateCommand: localSHA === remoteSHA ? null : skillUpdateCommand(install.scope, manager)
37530
+ };
37531
+ } catch {
37532
+ return {
37533
+ scope: install.scope,
37534
+ status: "unknown",
37535
+ updateCommand: null
37536
+ };
37537
+ }
37538
+ };
37539
+ const refreshUpdateStatus = async (deps = {}) => {
37540
+ const [cli, skill] = await Promise.all([checkCLIUpdate(deps), checkSkillUpdate(deps)]);
37541
+ return {
37542
+ checkedAt: deps.now?.() ?? Date.now(),
37543
+ cli,
37544
+ skill
37545
+ };
37546
+ };
37547
+ const readCachedUpdateStatus = async (home) => {
37548
+ try {
37549
+ const parsed = CacheFile.safeParse(JSON.parse(await readFile(updateCachePath(home), "utf8")));
37550
+ return parsed.success ? parsed.data : void 0;
37551
+ } catch {
37552
+ return;
37553
+ }
37554
+ };
37555
+ const writeCachedUpdateStatus = async (home, status) => {
37556
+ const path = updateCachePath(home);
37557
+ await mkdir(dirname(path), { recursive: true });
37558
+ await writeFile(path, `${JSON.stringify(status, null, 2)}\n`, { mode: 384 });
37559
+ await chmod(path, 384);
37560
+ };
37561
+ const refreshUpdateStatusCache = async (deps = {}) => {
37562
+ const status = await refreshUpdateStatus(deps);
37563
+ const cached = await readCachedUpdateStatus(deps.home);
37564
+ await writeCachedUpdateStatus(deps.home, {
37565
+ ...status,
37566
+ notices: cached?.notices
37567
+ });
37568
+ return status;
37569
+ };
37570
+ const isUpdateCacheStale = (cache, now) => !cache || now - cache.checkedAt >= UPDATE_CACHE_TTL_MS;
37571
+ const noticeDue = (cache, key, now) => now - (cache.notices?.[key] ?? 0) >= UPDATE_NOTICE_TTL_MS;
37572
+ const withNotice = (cache, key, now) => ({
37573
+ ...cache,
37574
+ notices: {
37575
+ ...cache.notices ?? {},
37576
+ [key]: now
37577
+ }
37578
+ });
37579
+ const noticeLinesFor = (cache, now) => {
37580
+ const notices = [];
37581
+ if (cache.cli.status === "update_available" && cache.cli.latestVersion && cache.cli.updateCommand) {
37582
+ const key = `cli:${cache.cli.latestVersion}`;
37583
+ if (noticeDue(cache, key, now)) notices.push({
37584
+ key,
37585
+ line: `Update available: social CLI ${cache.cli.latestVersion} (current ${cache.cli.currentVersion}). Run: ${cache.cli.updateCommand}`
37586
+ });
37587
+ }
37588
+ if (cache.skill.status === "update_available" && cache.skill.scope && cache.skill.updateCommand && cache.skill.remoteTreeSHA) {
37589
+ const key = `skill:${cache.skill.scope}:${cache.skill.remoteTreeSHA}`;
37590
+ if (noticeDue(cache, key, now)) notices.push({
37591
+ key,
37592
+ line: `Update available: social skill (${cache.skill.scope}). Run: ${cache.skill.updateCommand}`
37593
+ });
37594
+ }
37595
+ return notices;
37596
+ };
37597
+ const spawnInternalProbe = (packageRoot) => {
37598
+ const sourceEntry = join(packageRoot, "src", "index.ts");
37599
+ const distEntry = join(packageRoot, "bin", "social.mjs");
37600
+ const entry = process.versions.bun && existsSync(sourceEntry) ? sourceEntry : distEntry;
37601
+ spawn(process.execPath, [entry], {
37602
+ detached: true,
37603
+ env: {
37604
+ ...process.env,
37605
+ [INTERNAL_UPDATE_PROBE_ENV]: "1"
37606
+ },
37607
+ stdio: "ignore"
37608
+ }).unref();
37609
+ };
37610
+ const maybeRunUpdateAwareness = async (deps = {}) => {
37611
+ if (!updateAwarenessEnabled()) return;
37612
+ const now = deps.now?.() ?? Date.now();
37613
+ const home = deps.home;
37614
+ const cache = await readCachedUpdateStatus(home);
37615
+ if (cache) {
37616
+ let next = cache;
37617
+ for (const notice of noticeLinesFor(cache, now)) {
37618
+ printStderrLine(notice.line);
37619
+ next = withNotice(next, notice.key, now);
37620
+ }
37621
+ if (next !== cache) await writeCachedUpdateStatus(home, next);
37622
+ }
37623
+ if (isUpdateCacheStale(cache, now)) spawnInternalProbe(deps.packageRoot ?? internalPackageRoot());
37624
+ };
37625
+ const runInternalUpdateProbeIfRequested = async (deps = {}) => {
37626
+ if (process.env[INTERNAL_UPDATE_PROBE_ENV] !== "1") return false;
37627
+ try {
37628
+ await refreshUpdateStatusCache(deps);
37629
+ } catch {}
37630
+ return true;
37631
+ };
37632
+ const updateCommand = defineCommand({
37633
+ meta: commandMeta({
37634
+ name: "update",
37635
+ description: "Check for social CLI and skill updates.",
37636
+ contract: localCLIContract({
37637
+ outputSchema: object({
37638
+ cli: object({
37639
+ currentVersion: string(),
37640
+ latestVersion: string().nullable(),
37641
+ status: _enum([
37642
+ "current",
37643
+ "update_available",
37644
+ "unknown"
37645
+ ]),
37646
+ updateCommand: string().nullable()
37647
+ }),
37648
+ skill: object({
37649
+ scope: _enum(["project", "global"]).nullable(),
37650
+ status: _enum([
37651
+ "current",
37652
+ "not_installed",
37653
+ "update_available",
37654
+ "unknown"
37655
+ ]),
37656
+ updateCommand: string().nullable()
37657
+ })
37658
+ }),
37659
+ responseShaping: { formats: ["json"] }
37660
+ })
37661
+ }),
37662
+ run: async () => {
37663
+ const status = await refreshUpdateStatus();
37664
+ printData({
37665
+ cli: {
37666
+ currentVersion: status.cli.currentVersion,
37667
+ latestVersion: status.cli.latestVersion,
37668
+ status: status.cli.status,
37669
+ updateCommand: status.cli.updateCommand
37670
+ },
37671
+ skill: {
37672
+ scope: status.skill.scope,
37673
+ status: status.skill.status,
37674
+ updateCommand: status.skill.updateCommand
37675
+ }
37676
+ });
37677
+ }
37678
+ });
37679
+ //#endregion
37164
37680
  //#region src/index.ts
37165
37681
  const originalEmitWarning = process.emitWarning.bind(process);
37166
37682
  process.emitWarning = ((warning, ...rest) => {
@@ -37351,6 +37867,7 @@ const createSocialCommand = (deps = createCLIDeps()) => {
37351
37867
  subCommands: {
37352
37868
  account: accountCommand,
37353
37869
  schema: schemaCommand,
37870
+ update: updateCommand,
37354
37871
  x: xCommand,
37355
37872
  linkedin: linkedinCommand,
37356
37873
  feedback: feedbackCommand
@@ -37397,6 +37914,10 @@ const runSocial = async ({ command = createSocialCommand(), now = Date.now, rawA
37397
37914
  captureCommandException(error, exitCode, telemetry, now);
37398
37915
  return await finish(exitCode, telemetry, now);
37399
37916
  }
37917
+ if (path[0] !== "update") await maybeRunUpdateAwareness({
37918
+ now,
37919
+ packageRoot: dirname(dirname(fileURLToPath(import.meta.url)))
37920
+ });
37400
37921
  return await finish(0, telemetry, now);
37401
37922
  };
37402
37923
  const isEPIPEError = (error) => error instanceof Error && "code" in error && error.code === "EPIPE";
@@ -37447,6 +37968,7 @@ const installProcessHandlers = (telemetry) => {
37447
37968
  process.stderr.on("error", handlePipeError);
37448
37969
  };
37449
37970
  const main = async () => {
37971
+ if (await runInternalUpdateProbeIfRequested()) process.exit(0);
37450
37972
  installProcessHandlers(analytics);
37451
37973
  process.exit(await runSocial());
37452
37974
  };