offworld 0.3.3 → 0.3.5

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.
@@ -6,7 +6,7 @@ import { z } from "zod";
6
6
  import * as p from "@clack/prompts";
7
7
  import { NotLoggedInError, Paths, RepoExistsError, TokenExpiredError, cleanShellConfig, clearAuthData, cloneRepo, detectInstallMethod, detectInstalledAgents, discoverRepos, executeUninstall, executeUpgrade, fetchLatestVersion, gcRepos, getAllAgentConfigs, getAuthPath, getAuthStatus, getClonedRepoPath, getCommitDistance, getCommitSha, getConfigPath, getCurrentVersion, getMapEntry, getMetaPath, getMetaRoot, getProvider, getReferencePath, getRepoRoot, getRepoStatus, getShellConfigFiles, getToken, installGlobalSkill, installReference, isRepoCloned, listProviders, listRepos, loadAuthData, loadConfig, matchDependenciesToReferencesWithRemoteCheck, parseDependencies, parseRepoInput, pruneRepos, readGlobalMap, removeRepo, resolveDependencyRepo, resolveReferenceKeywords, saveAuthData, saveConfig, searchMap, toReferenceFileName, toReferenceName, updateAgentFiles, updateAllRepos, updateRepo, validateProviderModel, writeGlobalMap, writeProjectMap } from "@offworld/sdk/internal";
8
8
  import { AuthenticationError, CommitExistsError, CommitNotFoundError, GitHubError, InvalidInputError, InvalidReferenceError, LowStarsError, PrivateRepoError, RateLimitError, RepoNotFoundError, checkRemote, checkRemoteByName, pullReference, pullReferenceByName, pushReference } from "@offworld/sdk/sync";
9
- import { generateReferenceWithAI } from "@offworld/sdk/ai";
9
+ import { createOpenCodeContext, generateReferenceWithAI } from "@offworld/sdk/ai";
10
10
  import { ReferenceMetaSchema, WorkOSAuthErrorResponseSchema, WorkOSDeviceAuthResponseSchema, WorkOSTokenResponseSchema } from "@offworld/types";
11
11
  import { homedir } from "node:os";
12
12
  import { AgentSchema, ConfigSchema } from "@offworld/types/schemas";
@@ -146,7 +146,7 @@ function parseModelFlag$1(model) {
146
146
  return { model };
147
147
  }
148
148
  async function pullHandler(options) {
149
- const { repo, shallow = false, sparse = false, branch, force = false, verbose = false, quiet = false, skipConfirm = false, onProgress } = options;
149
+ const { repo, sparse = false, branch, force = false, verbose = false, allowGenerate = true, skipUpdate = false, quiet = false, skipConfirm = false, onProgress } = options;
150
150
  const referenceName = options.reference?.trim() || void 0;
151
151
  const { provider, model } = parseModelFlag$1(options.model);
152
152
  const config = loadConfig();
@@ -158,7 +158,7 @@ async function pullHandler(options) {
158
158
  } : createSpinner({ silent: quiet });
159
159
  const log = quiet ? () => {} : (msg) => p.log.info(msg);
160
160
  const logSuccess = quiet ? () => {} : (msg) => p.log.success(msg);
161
- if (verbose && !quiet) p.log.info(`[verbose] Options: repo=${repo}, reference=${referenceName ?? "default"}, shallow=${shallow}, branch=${branch || "default"}, force=${force}`);
161
+ if (verbose && !quiet) p.log.info(`[verbose] Options: repo=${repo}, reference=${referenceName ?? "default"}, branch=${branch || "default"}, force=${force}`);
162
162
  try {
163
163
  s.start("Parsing repository input...");
164
164
  const source = parseRepoInput(repo);
@@ -167,17 +167,20 @@ async function pullHandler(options) {
167
167
  let repoPath;
168
168
  if (source.type === "remote") {
169
169
  const qualifiedName = source.qualifiedName;
170
- if (isRepoCloned(qualifiedName)) {
170
+ if (isRepoCloned(qualifiedName)) if (skipUpdate) {
171
+ repoPath = getClonedRepoPath(qualifiedName);
172
+ s.stop("Using existing clone");
173
+ } else {
171
174
  s.start("Updating repository...");
172
175
  const result = await updateRepo(qualifiedName);
173
176
  repoPath = getClonedRepoPath(qualifiedName);
174
177
  if (result.updated) s.stop(`Updated (${result.previousSha.slice(0, 7)} → ${result.currentSha.slice(0, 7)})`);
175
178
  else s.stop("Already up to date");
176
- } else {
179
+ }
180
+ else {
177
181
  s.start(`Cloning ${source.fullName}...`);
178
182
  try {
179
183
  repoPath = await cloneRepo(source, {
180
- shallow,
181
184
  sparse,
182
185
  branch,
183
186
  config,
@@ -218,19 +221,23 @@ async function pullHandler(options) {
218
221
  const remoteSha = remoteCheck.commitSha;
219
222
  const remoteShaNorm = remoteSha.slice(0, 7);
220
223
  const currentShaNorm = currentSha.slice(0, 7);
221
- const MAX_COMMIT_DISTANCE = 20;
224
+ const maxCommitDistance = config.maxCommitDistance ?? 20;
225
+ const acceptUnknownDistance = config.acceptUnknownDistance ?? false;
222
226
  const commitDistance = getCommitDistance(repoPath, remoteSha, currentSha);
223
- if (isReferenceOverride || remoteShaNorm === currentShaNorm || commitDistance !== null && commitDistance <= MAX_COMMIT_DISTANCE) {
224
- if (!isReferenceOverride) if (commitDistance === 0 || remoteShaNorm === currentShaNorm) {
227
+ const isExactMatch = remoteShaNorm === currentShaNorm || commitDistance === 0;
228
+ const isWithinDistance = commitDistance !== null && commitDistance <= maxCommitDistance;
229
+ const hasUnknownDistance = commitDistance === null;
230
+ if (isReferenceOverride || isExactMatch || isWithinDistance || acceptUnknownDistance && hasUnknownDistance) {
231
+ if (!isReferenceOverride) if (isExactMatch) {
225
232
  verboseLog(`Remote SHA matches (${remoteShaNorm})`, verbose);
226
233
  s.stop("Remote reference found (exact match)");
227
- } else if (commitDistance !== null) {
228
- verboseLog(`Remote reference is ${commitDistance} commits behind (within ${MAX_COMMIT_DISTANCE} threshold)`, verbose);
234
+ } else if (isWithinDistance) {
235
+ verboseLog(`Remote reference is ${commitDistance} commits behind (within ${maxCommitDistance} threshold)`, verbose);
229
236
  s.stop(`Remote reference found (${commitDistance} commits behind)`);
230
- } else {
231
- verboseLog("Remote reference found (commit distance unknown)", verbose);
237
+ } else if (hasUnknownDistance) {
238
+ verboseLog(acceptUnknownDistance ? "Remote reference found (distance unknown, accepted)" : "Remote reference found (commit distance unknown)", verbose);
232
239
  s.stop("Remote reference found");
233
- }
240
+ } else s.stop("Remote reference found");
234
241
  else s.stop("Remote reference found");
235
242
  log(`Preview: ${referenceName ? `https://offworld.sh/${source.fullName}/${encodeURIComponent(referenceName)}` : `https://offworld.sh/${source.fullName}`}`);
236
243
  let useRemote = true;
@@ -266,7 +273,7 @@ async function pullHandler(options) {
266
273
  }
267
274
  } else {
268
275
  const distanceInfo = commitDistance !== null ? ` (${commitDistance} commits behind)` : "";
269
- verboseLog(`Remote reference too outdated${distanceInfo}, threshold is ${MAX_COMMIT_DISTANCE}`, verbose);
276
+ verboseLog(`Remote reference too outdated${distanceInfo}, threshold is ${maxCommitDistance}`, verbose);
270
277
  s.stop(`Remote reference outdated${distanceInfo}`);
271
278
  }
272
279
  } else s.stop("No remote reference found");
@@ -277,17 +284,30 @@ async function pullHandler(options) {
277
284
  }
278
285
  }
279
286
  if (isReferenceOverride) throw new Error(`Reference not found on offworld.sh: ${referenceName}`);
287
+ if (!allowGenerate && source.type === "remote") {
288
+ const message = "Remote reference unavailable, outdated, or declined; local generation is disabled.";
289
+ s.stop(message);
290
+ return {
291
+ success: false,
292
+ repoPath,
293
+ referenceSource: "local",
294
+ referenceInstalled: false,
295
+ message
296
+ };
297
+ }
280
298
  verboseLog(`Starting AI reference generation for: ${repoPath}`, verbose);
281
299
  if (!verbose) s.start("Generating reference with AI...");
282
300
  try {
301
+ const onDebug = verbose ? (message) => {
302
+ p.log.info(`[${timestamp()}] [debug] ${message}`);
303
+ } : (msg) => {
304
+ if (!msg.startsWith("[")) s.message(msg);
305
+ };
283
306
  const { referenceContent, commitSha: referenceCommitSha } = await generateReferenceWithAI(repoPath, qualifiedName, {
284
307
  provider,
285
308
  model,
286
- onDebug: verbose ? (message) => {
287
- p.log.info(`[${timestamp()}] [debug] ${message}`);
288
- } : (msg) => {
289
- if (!msg.startsWith("[")) s.message(msg);
290
- }
309
+ openCodeContext: options.openCodeContext,
310
+ onDebug
291
311
  });
292
312
  const meta = {
293
313
  referenceUpdatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -370,10 +390,7 @@ async function generateHandler(options) {
370
390
  p.log.info(`Using existing clone at ${repoPath}`);
371
391
  } else {
372
392
  s.start(`Cloning ${source.fullName}...`);
373
- repoPath = await cloneRepo(source, {
374
- shallow: config.defaultShallow,
375
- config
376
- });
393
+ repoPath = await cloneRepo(source, { config });
377
394
  s.stop("Repository cloned");
378
395
  }
379
396
  } else repoPath = source.path;
@@ -466,13 +483,12 @@ async function repoListHandler(options) {
466
483
  return { repos: items };
467
484
  }
468
485
  async function repoUpdateHandler(options) {
469
- const { all = false, pattern, dryRun = false, unshallow = false } = options;
486
+ const { all = false, pattern, dryRun = false } = options;
470
487
  if (!all && !pattern) {
471
488
  p.log.error("Specify --all or a pattern to update.");
472
489
  return {
473
490
  updated: [],
474
491
  skipped: [],
475
- unshallowed: [],
476
492
  errors: []
477
493
  };
478
494
  }
@@ -483,19 +499,17 @@ async function repoUpdateHandler(options) {
483
499
  return {
484
500
  updated: [],
485
501
  skipped: [],
486
- unshallowed: [],
487
502
  errors: []
488
503
  };
489
504
  }
490
505
  let processed = 0;
491
506
  const outcomes = [];
492
507
  const spinner = p.spinner();
493
- const action = unshallow ? "Unshallowing" : "Updating";
508
+ const action = "Updating";
494
509
  spinner.start(dryRun ? `Checking ${total} repos...` : `${action} ${total} repos...`);
495
510
  const result = await updateAllRepos({
496
511
  pattern,
497
512
  dryRun,
498
- unshallow,
499
513
  onProgress: (repo, status, message) => {
500
514
  if (status === "updating") {
501
515
  processed++;
@@ -509,12 +523,10 @@ async function repoUpdateHandler(options) {
509
523
  });
510
524
  spinner.stop(dryRun ? "Dry run complete" : `${action} complete`);
511
525
  for (const { repo, status, message } of outcomes) if (status === "updated") p.log.success(`${repo}${message ? ` (${message})` : ""}`);
512
- else if (status === "unshallowed") p.log.success(`${repo}: ${message}`);
513
526
  else if (status === "error") p.log.error(`${repo}: ${message}`);
514
527
  else if (status === "skipped" && message !== "up to date" && message !== "already up to date") p.log.warn(`${repo}: ${message}`);
515
528
  const parts = [];
516
529
  if (result.updated.length > 0) parts.push(`${result.updated.length} ${dryRun ? "would update" : "updated"}`);
517
- if (result.unshallowed.length > 0) parts.push(`${result.unshallowed.length} unshallowed`);
518
530
  if (result.skipped.length > 0) parts.push(`${result.skipped.length} skipped`);
519
531
  if (result.errors.length > 0) parts.push(`${result.errors.length} failed`);
520
532
  if (parts.length > 0) p.log.info(`Summary: ${parts.join(", ")}`);
@@ -1155,8 +1167,9 @@ async function handleReferenceOnlyRemoval(repoName, yes, dryRun) {
1155
1167
  */
1156
1168
  const VALID_KEYS = [
1157
1169
  "repoRoot",
1158
- "defaultShallow",
1159
1170
  "defaultModel",
1171
+ "maxCommitDistance",
1172
+ "acceptUnknownDistance",
1160
1173
  "agents"
1161
1174
  ];
1162
1175
  function isValidKey(key) {
@@ -1201,16 +1214,7 @@ async function configSetHandler(options) {
1201
1214
  };
1202
1215
  }
1203
1216
  let parsedValue;
1204
- if (key === "defaultShallow") if (value === "true" || value === "1") parsedValue = true;
1205
- else if (value === "false" || value === "0") parsedValue = false;
1206
- else {
1207
- p.log.error(`Invalid boolean value: ${value}. Use 'true' or 'false'.`);
1208
- return {
1209
- success: false,
1210
- message: `Invalid boolean value: ${value}`
1211
- };
1212
- }
1213
- else if (key === "agents") {
1217
+ if (key === "agents") {
1214
1218
  const agentValues = value.split(",").map((a) => a.trim()).filter(Boolean);
1215
1219
  const agentsResult = z.array(AgentSchema).safeParse(agentValues);
1216
1220
  if (!agentsResult.success) {
@@ -1223,6 +1227,26 @@ async function configSetHandler(options) {
1223
1227
  };
1224
1228
  }
1225
1229
  parsedValue = agentsResult.data;
1230
+ } else if (key === "maxCommitDistance") {
1231
+ const parsed = Number.parseInt(value, 10);
1232
+ if (Number.isNaN(parsed) || parsed < 0) {
1233
+ p.log.error("maxCommitDistance must be a non-negative integer.");
1234
+ return {
1235
+ success: false,
1236
+ message: "Invalid maxCommitDistance value"
1237
+ };
1238
+ }
1239
+ parsedValue = parsed;
1240
+ } else if (key === "acceptUnknownDistance") {
1241
+ const normalized = value.trim().toLowerCase();
1242
+ if (normalized !== "true" && normalized !== "false") {
1243
+ p.log.error("acceptUnknownDistance must be 'true' or 'false'.");
1244
+ return {
1245
+ success: false,
1246
+ message: "Invalid acceptUnknownDistance value"
1247
+ };
1248
+ }
1249
+ parsedValue = normalized === "true";
1226
1250
  } else parsedValue = value;
1227
1251
  try {
1228
1252
  saveConfig({ [key]: parsedValue });
@@ -1806,6 +1830,27 @@ async function initHandler(options = {}) {
1806
1830
 
1807
1831
  //#endregion
1808
1832
  //#region src/handlers/project.ts
1833
+ function isInternalDependencyVersion(version) {
1834
+ if (!version) return false;
1835
+ const trimmed = version.trim();
1836
+ if (!trimmed) return false;
1837
+ const isPathReference = trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith("/") || trimmed.startsWith("~/") || trimmed.startsWith("~\\");
1838
+ return trimmed.startsWith("workspace:") || trimmed.startsWith("file:") || trimmed.startsWith("link:") || trimmed.startsWith("portal:") || isPathReference;
1839
+ }
1840
+ function dedupeMatchesByRepo(matches) {
1841
+ const seenRepos = /* @__PURE__ */ new Set();
1842
+ const deduped = [];
1843
+ for (const match of matches) {
1844
+ if (!match.repo) {
1845
+ deduped.push(match);
1846
+ continue;
1847
+ }
1848
+ if (seenRepos.has(match.repo)) continue;
1849
+ seenRepos.add(match.repo);
1850
+ deduped.push(match);
1851
+ }
1852
+ return deduped;
1853
+ }
1809
1854
  function detectProjectRoot() {
1810
1855
  let currentDir = process.cwd();
1811
1856
  while (currentDir !== homedir()) {
@@ -1831,6 +1876,20 @@ function showBanner() {
1831
1876
  for (const line of LOGO_LINES) console.log(`${OLIVE}${line}${RESET}`);
1832
1877
  console.log();
1833
1878
  }
1879
+ async function runWithConcurrency(tasks, concurrency) {
1880
+ if (concurrency < 1) throw new Error("Concurrency must be at least 1.");
1881
+ const results = [];
1882
+ let index = 0;
1883
+ async function worker() {
1884
+ while (index < tasks.length) {
1885
+ const i = index++;
1886
+ results[i] = await tasks[i]();
1887
+ }
1888
+ }
1889
+ const workers = Array.from({ length: Math.min(concurrency, tasks.length) }, () => worker());
1890
+ await Promise.all(workers);
1891
+ return results;
1892
+ }
1834
1893
  async function projectInitHandler(options = {}) {
1835
1894
  showBanner();
1836
1895
  p.intro("Scan your deps to install reference files and create a clone map for your agents.");
@@ -1865,11 +1924,7 @@ async function projectInitHandler(options = {}) {
1865
1924
  p.log.info(`Found ${pc.cyan(dependencies.length)} dependencies`);
1866
1925
  if (dependencies.length > 10) p.log.warn("Generating many references at once uses a lot of tokens. Consider selecting just a few.");
1867
1926
  p.log.step("Resolving GitHub repositories...");
1868
- const isInternalDep = (version) => {
1869
- if (!version) return false;
1870
- return version.startsWith("workspace:") || version.startsWith("catalog:") || version.startsWith("file:") || version.startsWith("link:");
1871
- };
1872
- const resolvedPromises = dependencies.filter((dep) => !isInternalDep(dep.version)).map((dep) => resolveDependencyRepo(dep.name));
1927
+ const resolvedPromises = dependencies.filter((dep) => !isInternalDependencyVersion(dep.version)).map((dep) => resolveDependencyRepo(dep.name, dep.version));
1873
1928
  const resolved = await Promise.all(resolvedPromises);
1874
1929
  const skipList = options.skip ? options.skip.split(",").map((d) => d.trim()) : [];
1875
1930
  const depsList = options.deps ? options.deps.split(",").map((d) => d.trim()) : [];
@@ -1944,6 +1999,12 @@ async function projectInitHandler(options = {}) {
1944
1999
  }
1945
2000
  selected = Array.isArray(selectedResult) ? selectedResult : [];
1946
2001
  }
2002
+ const dedupedSelected = dedupeMatchesByRepo(selected);
2003
+ if (dedupedSelected.length !== selected.length) {
2004
+ const dedupeCount = selected.length - dedupedSelected.length;
2005
+ p.log.info(`Deduped ${dedupeCount} duplicate dependencies that map to the same repository.`);
2006
+ }
2007
+ selected = dedupedSelected;
1947
2008
  if (selected.length === 0) {
1948
2009
  p.log.warn("No dependencies selected.");
1949
2010
  p.outro("");
@@ -1972,55 +2033,164 @@ async function projectInitHandler(options = {}) {
1972
2033
  message: "Dry run complete"
1973
2034
  };
1974
2035
  }
2036
+ const requestedConcurrency = options.concurrency ?? 4;
2037
+ const concurrency = Number.isFinite(requestedConcurrency) && requestedConcurrency > 0 ? Math.max(1, Math.trunc(requestedConcurrency)) : 4;
2038
+ const installedGroup = selected.filter((m) => m.status === "installed");
2039
+ const remoteGroup = selected.filter((m) => m.status === "remote");
2040
+ const generateGroup = selected.filter((m) => m.status === "generate");
2041
+ const total = selected.length;
1975
2042
  const installed = [];
2043
+ const successfulMatches = [];
1976
2044
  let failedCount = 0;
1977
- const total = selected.length;
1978
- for (let i = 0; i < selected.length; i++) {
1979
- const match = selected[i];
1980
- if (!match.repo) continue;
1981
- const repo = match.repo;
1982
- const prefix = `${`[${i + 1}/${total}]`} ${match.dep}`;
1983
- const spinner = p.spinner();
1984
- spinner.start(`${prefix}: Starting...`);
1985
- try {
1986
- const pullResult = await pullHandler({
1987
- repo,
1988
- shallow: true,
1989
- force: options.generate,
1990
- verbose: false,
1991
- quiet: true,
1992
- skipConfirm: true,
1993
- onProgress: (msg) => spinner.message(`${prefix}: ${msg}`)
1994
- });
1995
- if (pullResult.success && pullResult.referenceInstalled) {
2045
+ let counter = 0;
2046
+ if (installedGroup.length > 0) {
2047
+ p.log.step(`Adding ${pc.blue(installedGroup.length)} already-installed references...`);
2048
+ await runWithConcurrency(installedGroup.map((match) => async () => {
2049
+ const i = ++counter;
2050
+ const repo = match.repo;
2051
+ const prefix = `${`[${i}/${total}]`} ${match.dep}`;
2052
+ try {
1996
2053
  const referencePath = getReferencePath(repo);
2054
+ successfulMatches.push(match);
1997
2055
  installed.push({
1998
2056
  dependency: match.dep,
1999
2057
  reference: toReferenceFileName(repo),
2000
2058
  path: referencePath
2001
2059
  });
2002
- const source = pullResult.referenceSource === "remote" ? "downloaded" : "generated";
2003
- spinner.stop(`${prefix} ${pc.green("✓")} ${pc.dim(`(${source})`)}`);
2004
- } else {
2060
+ p.log.info(`${prefix} ${pc.green("")} ${pc.dim("(already installed)")}`);
2061
+ return {
2062
+ match,
2063
+ success: true,
2064
+ source: "installed"
2065
+ };
2066
+ } catch (error) {
2067
+ const errMsg = error instanceof Error ? error.message : "Unknown error";
2068
+ p.log.info(`${prefix} ${pc.red("✗")} ${pc.dim(errMsg)}`);
2069
+ failedCount++;
2070
+ return {
2071
+ match,
2072
+ success: false
2073
+ };
2074
+ }
2075
+ }), concurrency);
2076
+ }
2077
+ if (remoteGroup.length > 0) {
2078
+ p.log.step(`Downloading ${pc.green(remoteGroup.length)} remote references...`);
2079
+ await runWithConcurrency(remoteGroup.map((match) => async () => {
2080
+ const i = ++counter;
2081
+ const repo = match.repo;
2082
+ const prefix = `${`[${i}/${total}]`} ${match.dep}`;
2083
+ const spinner = p.spinner();
2084
+ spinner.start(`${prefix}: Downloading...`);
2085
+ try {
2086
+ const pullResult = await pullHandler({
2087
+ repo,
2088
+ force: false,
2089
+ verbose: false,
2090
+ allowGenerate: false,
2091
+ quiet: true,
2092
+ skipConfirm: true,
2093
+ skipUpdate: true,
2094
+ onProgress: (msg) => spinner.message(`${prefix}: ${msg}`)
2095
+ });
2096
+ if (pullResult.success && pullResult.referenceInstalled) {
2097
+ const referencePath = getReferencePath(repo);
2098
+ successfulMatches.push(match);
2099
+ installed.push({
2100
+ dependency: match.dep,
2101
+ reference: toReferenceFileName(repo),
2102
+ path: referencePath
2103
+ });
2104
+ const source = pullResult.referenceSource === "remote" ? "downloaded" : pullResult.referenceSource;
2105
+ spinner.stop(`${prefix} ${pc.green("✓")} ${pc.dim(`(${source})`)}`);
2106
+ return {
2107
+ match,
2108
+ success: true,
2109
+ source: "remote"
2110
+ };
2111
+ }
2005
2112
  spinner.stop(`${prefix} ${pc.red("✗")} ${pc.red("failed")}`);
2006
2113
  failedCount++;
2114
+ return {
2115
+ match,
2116
+ success: false
2117
+ };
2118
+ } catch (error) {
2119
+ const errMsg = error instanceof Error ? error.message : "Unknown error";
2120
+ spinner.stop(`${prefix} ${pc.red("✗")} ${pc.dim(errMsg)}`);
2121
+ failedCount++;
2122
+ return {
2123
+ match,
2124
+ success: false
2125
+ };
2007
2126
  }
2008
- } catch (error) {
2009
- const errMsg = error instanceof Error ? error.message : "Unknown error";
2010
- spinner.stop(`${prefix} ${pc.red("✗")} ${pc.dim(errMsg)}`);
2011
- failedCount++;
2012
- }
2127
+ }), concurrency);
2013
2128
  }
2014
- const map = readGlobalMap();
2015
- writeProjectMap(projectRoot, Object.fromEntries(selected.filter((m) => m.repo).map((m) => {
2016
- const qualifiedName = `github.com:${m.repo}`;
2017
- const entry = map.repos[qualifiedName];
2018
- return [qualifiedName, {
2019
- localPath: entry?.localPath ?? "",
2020
- reference: toReferenceFileName(m.repo),
2021
- keywords: entry?.keywords ?? []
2022
- }];
2023
- })));
2129
+ if (generateGroup.length > 0 && options.generate) {
2130
+ p.log.step(`Generating ${pc.yellow(generateGroup.length)} references with AI...`);
2131
+ let openCodeContext;
2132
+ try {
2133
+ if (generateGroup.length > 1) {
2134
+ p.log.info(pc.dim("Starting shared AI server for batch generation..."));
2135
+ openCodeContext = await createOpenCodeContext({ onDebug: () => {} });
2136
+ }
2137
+ for (const match of generateGroup) {
2138
+ const i = ++counter;
2139
+ const repo = match.repo;
2140
+ const prefix = `${`[${i}/${total}]`} ${match.dep}`;
2141
+ const spinner = p.spinner();
2142
+ spinner.start(`${prefix}: Starting...`);
2143
+ try {
2144
+ const pullResult = await pullHandler({
2145
+ repo,
2146
+ force: options.generate,
2147
+ verbose: false,
2148
+ quiet: true,
2149
+ skipConfirm: true,
2150
+ openCodeContext,
2151
+ onProgress: (msg) => spinner.message(`${prefix}: ${msg}`)
2152
+ });
2153
+ if (pullResult.success && pullResult.referenceInstalled) {
2154
+ const referencePath = getReferencePath(repo);
2155
+ successfulMatches.push(match);
2156
+ installed.push({
2157
+ dependency: match.dep,
2158
+ reference: toReferenceFileName(repo),
2159
+ path: referencePath
2160
+ });
2161
+ const source = pullResult.referenceSource === "remote" ? "downloaded" : "generated";
2162
+ spinner.stop(`${prefix} ${pc.green("✓")} ${pc.dim(`(${source})`)}`);
2163
+ } else {
2164
+ spinner.stop(`${prefix} ${pc.red("✗")} ${pc.red("failed")}`);
2165
+ failedCount++;
2166
+ }
2167
+ } catch (error) {
2168
+ const errMsg = error instanceof Error ? error.message : "Unknown error";
2169
+ spinner.stop(`${prefix} ${pc.red("✗")} ${pc.dim(errMsg)}`);
2170
+ failedCount++;
2171
+ }
2172
+ }
2173
+ } finally {
2174
+ openCodeContext?.close();
2175
+ }
2176
+ } else if (generateGroup.length > 0 && !options.generate) p.log.info(`Skipping ${pc.yellow(generateGroup.length)} deps that need generation (use --generate to include them).`);
2177
+ if (successfulMatches.length > 0) {
2178
+ const map = readGlobalMap();
2179
+ const successfulRepos = /* @__PURE__ */ new Map();
2180
+ for (const match of successfulMatches) {
2181
+ if (!match.repo || successfulRepos.has(match.repo)) continue;
2182
+ successfulRepos.set(match.repo, match);
2183
+ }
2184
+ writeProjectMap(projectRoot, Object.fromEntries(Array.from(successfulRepos.values()).map((match) => {
2185
+ const qualifiedName = `github.com:${match.repo}`;
2186
+ const entry = map.repos[qualifiedName];
2187
+ return [qualifiedName, {
2188
+ localPath: entry?.localPath ?? "",
2189
+ reference: toReferenceFileName(match.repo),
2190
+ keywords: entry?.keywords ?? []
2191
+ }];
2192
+ })));
2193
+ } else p.log.warn("No references were installed. Project map was not updated.");
2024
2194
  if (installed.length > 0) try {
2025
2195
  updateAgentFiles(projectRoot, installed);
2026
2196
  } catch (error) {
@@ -2302,12 +2472,11 @@ async function mapSearchHandler(options) {
2302
2472
 
2303
2473
  //#endregion
2304
2474
  //#region src/index.ts
2305
- const version = "0.3.3";
2475
+ const version = "0.3.5";
2306
2476
  const router = os.router({
2307
2477
  pull: os.input(z.object({
2308
2478
  repo: z.string().describe("repo").meta({ positional: true }),
2309
2479
  reference: z.string().optional().describe("Reference name to pull (defaults to owner-repo)").meta({ alias: "r" }),
2310
- shallow: z.boolean().default(false).describe("Use shallow clone (--depth 1)").meta({ negativeAlias: "full-history" }),
2311
2480
  sparse: z.boolean().default(false).describe("Use sparse checkout (only src/, lib/, packages/, docs/)"),
2312
2481
  branch: z.string().optional().describe("Branch to clone"),
2313
2482
  force: z.boolean().default(false).describe("Force re-generation").meta({ alias: "f" }),
@@ -2320,7 +2489,6 @@ const router = os.router({
2320
2489
  await pullHandler({
2321
2490
  repo: input.repo,
2322
2491
  reference: input.reference,
2323
- shallow: input.shallow,
2324
2492
  sparse: input.sparse,
2325
2493
  branch: input.branch,
2326
2494
  force: input.force,
@@ -2401,10 +2569,11 @@ const router = os.router({
2401
2569
  })).meta({ description: `Set a config value
2402
2570
 
2403
2571
  Valid keys:
2404
- repoRoot (string) Where to clone repos (e.g., ~/ow)
2405
- defaultShallow (boolean) Use shallow clone by default (true/false)
2406
- defaultModel (string) AI provider/model (e.g., anthropic/claude-sonnet-4-20250514)
2407
- agents (list) Comma-separated agents (e.g., claude-code,opencode)` }).handler(async ({ input }) => {
2572
+ repoRoot (string) Where to clone repos (e.g., ~/ow)
2573
+ defaultModel (string) AI provider/model (e.g., anthropic/claude-sonnet-4-20250514)
2574
+ maxCommitDistance (number) Max commit distance to accept remote references (default: 20)
2575
+ acceptUnknownDistance (boolean) Accept remote refs when distance is unknown (default: false)
2576
+ agents (list) Comma-separated agents (e.g., claude-code,opencode)` }).handler(async ({ input }) => {
2408
2577
  await configSetHandler({
2409
2578
  key: input.key,
2410
2579
  value: input.value
@@ -2412,7 +2581,7 @@ Valid keys:
2412
2581
  }),
2413
2582
  get: os.input(z.object({ key: z.string().describe("key").meta({ positional: true }) })).meta({ description: `Get a config value
2414
2583
 
2415
- Valid keys: repoRoot, defaultShallow, defaultModel, agents` }).handler(async ({ input }) => {
2584
+ Valid keys: repoRoot, defaultModel, maxCommitDistance, acceptUnknownDistance, agents` }).handler(async ({ input }) => {
2416
2585
  await configGetHandler({ key: input.key });
2417
2586
  }),
2418
2587
  reset: os.input(z.object({})).meta({ description: "Reset config to defaults" }).handler(async () => {
@@ -2446,7 +2615,8 @@ Valid keys: repoRoot, defaultShallow, defaultModel, agents` }).handler(async ({
2446
2615
  skip: z.string().optional().describe("Comma-separated deps to exclude"),
2447
2616
  generate: z.boolean().default(false).describe("Generate references for deps without existing ones").meta({ alias: "g" }),
2448
2617
  dryRun: z.boolean().default(false).describe("Show what would be done without doing it").meta({ alias: "d" }),
2449
- yes: z.boolean().default(false).describe("Skip confirmations").meta({ alias: "y" })
2618
+ yes: z.boolean().default(false).describe("Skip confirmations").meta({ alias: "y" }),
2619
+ concurrency: z.number().int().min(1).default(4).describe("Max parallel installs for remote/installed refs (min: 1)").meta({ alias: "c" })
2450
2620
  })).meta({
2451
2621
  description: "Scan manifest, install references, update AGENTS.md",
2452
2622
  default: true
@@ -2457,7 +2627,8 @@ Valid keys: repoRoot, defaultShallow, defaultModel, agents` }).handler(async ({
2457
2627
  skip: input.skip,
2458
2628
  generate: input.generate,
2459
2629
  dryRun: input.dryRun,
2460
- yes: input.yes
2630
+ yes: input.yes,
2631
+ concurrency: input.concurrency
2461
2632
  });
2462
2633
  }) }),
2463
2634
  map: os.router({
@@ -2508,14 +2679,12 @@ Valid keys: repoRoot, defaultShallow, defaultModel, agents` }).handler(async ({
2508
2679
  update: os.input(z.object({
2509
2680
  all: z.boolean().default(false).describe("Update all repos"),
2510
2681
  pattern: z.string().optional().describe("Filter by pattern"),
2511
- dryRun: z.boolean().default(false).describe("Show what would be updated").meta({ alias: "d" }),
2512
- unshallow: z.boolean().default(false).describe("Convert shallow clones to full clones")
2682
+ dryRun: z.boolean().default(false).describe("Show what would be updated").meta({ alias: "d" })
2513
2683
  })).meta({ description: "Update repos (git fetch + pull)" }).handler(async ({ input }) => {
2514
2684
  await repoUpdateHandler({
2515
2685
  all: input.all,
2516
2686
  pattern: input.pattern,
2517
- dryRun: input.dryRun,
2518
- unshallow: input.unshallow
2687
+ dryRun: input.dryRun
2519
2688
  });
2520
2689
  }),
2521
2690
  prune: os.input(z.object({
@@ -2602,4 +2771,4 @@ function createOwCli() {
2602
2771
 
2603
2772
  //#endregion
2604
2773
  export { router as n, version as r, createOwCli as t };
2605
- //# sourceMappingURL=src-CrZvPV0A.mjs.map
2774
+ //# sourceMappingURL=src-DAHGD773.mjs.map