offworld 0.3.4 → 0.3.6

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/README.md CHANGED
@@ -97,7 +97,6 @@ ow list
97
97
 
98
98
  ```
99
99
  --reference, -r Reference filename override
100
- --shallow Use shallow clone (--depth 1)
101
100
  --sparse Sparse checkout (src/, lib/, packages/, docs/)
102
101
  --branch <name> Branch to clone
103
102
  --force, -f Force regeneration
@@ -128,7 +127,7 @@ ow list
128
127
  --all Select all deps
129
128
  --deps Comma-separated deps
130
129
  --skip Deps to exclude
131
- --generate, -g Generate references for new deps
130
+ --generate, -g Force local generation for new deps
132
131
  --dry-run, -d Preview only
133
132
  --yes, -y Skip confirmations
134
133
  ```
@@ -150,12 +149,13 @@ ow list
150
149
 
151
150
  ## Config Keys
152
151
 
153
- | Key | Type | Description |
154
- | ---------------- | ------- | ----------------------------------------------------- |
155
- | `repoRoot` | string | Where to clone repos (default: `~/ow`) |
156
- | `defaultShallow` | boolean | Use shallow clone by default |
157
- | `defaultModel` | string | AI model (e.g., `anthropic/claude-sonnet-4-20250514`) |
158
- | `agents` | list | Comma-separated agent names |
152
+ | Key | Type | Description |
153
+ | ----------------------- | ------- | --------------------------------------------------------------------- |
154
+ | `repoRoot` | string | Where to clone repos (default: `~/ow`) |
155
+ | `defaultModel` | string | AI model (e.g., `anthropic/claude-sonnet-4-20250514`) |
156
+ | `maxCommitDistance` | number | Max commit distance to accept remote references (default: `20`) |
157
+ | `acceptUnknownDistance` | boolean | Accept remote refs when commit distance is unknown (default: `false`) |
158
+ | `agents` | list | Comma-separated agent names |
159
159
 
160
160
  ## Path Discovery
161
161
 
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { r as version, t as createOwCli } from "./src-BLm6ei_p.mjs";
2
+ import { r as version, t as createOwCli } from "./src-BN5uTbLS.mjs";
3
3
  import { existsSync, readFileSync } from "node:fs";
4
4
  import { dirname, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
package/dist/index.d.mts CHANGED
@@ -4,12 +4,11 @@ import { z } from "zod";
4
4
  import * as trpc_cli_dist_json_js0 from "trpc-cli/dist/json.js";
5
5
 
6
6
  //#region src/index.d.ts
7
- declare const version = "0.3.4";
7
+ declare const version = "0.3.6";
8
8
  declare const router: {
9
9
  pull: _orpc_server0.Procedure<_orpc_server0.MergedInitialContext<Record<never, never>, Record<never, never>, Record<never, never>>, Record<never, never>, z.ZodObject<{
10
10
  repo: z.ZodString;
11
11
  reference: z.ZodOptional<z.ZodString>;
12
- shallow: z.ZodDefault<z.ZodBoolean>;
13
12
  sparse: z.ZodDefault<z.ZodBoolean>;
14
13
  branch: z.ZodOptional<z.ZodString>;
15
14
  force: z.ZodDefault<z.ZodBoolean>;
@@ -71,6 +70,7 @@ declare const router: {
71
70
  generate: z.ZodDefault<z.ZodBoolean>;
72
71
  dryRun: z.ZodDefault<z.ZodBoolean>;
73
72
  yes: z.ZodDefault<z.ZodBoolean>;
73
+ concurrency: z.ZodDefault<z.ZodNumber>;
74
74
  }, z.core.$strip>, _orpc_server0.Schema<void, void>, _orpc_server0.MergedErrorMap<Record<never, never>, _orpc_server0.MergedErrorMap<Record<never, never>, Record<never, never>>>, Record<never, never>>;
75
75
  };
76
76
  map: {
@@ -96,7 +96,6 @@ declare const router: {
96
96
  all: z.ZodDefault<z.ZodBoolean>;
97
97
  pattern: z.ZodOptional<z.ZodString>;
98
98
  dryRun: z.ZodDefault<z.ZodBoolean>;
99
- unshallow: z.ZodDefault<z.ZodBoolean>;
100
99
  }, z.core.$strip>, _orpc_server0.Schema<void, void>, _orpc_server0.MergedErrorMap<Record<never, never>, _orpc_server0.MergedErrorMap<Record<never, never>, Record<never, never>>>, Record<never, never>>;
101
100
  prune: _orpc_server0.Procedure<_orpc_server0.MergedInitialContext<Record<never, never>, _orpc_server0.MergedInitialContext<Record<never, never>, Record<never, never>, Record<never, never>>, Record<never, never>>, Record<never, never>, z.ZodObject<{
102
101
  dryRun: z.ZodDefault<z.ZodBoolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;cA+Ba,OAAA;AAAA,cAEA,MAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAugBG,WAAA,CAAA;0BAAW,SAAA,CAAA,gBAAA;;+BA5OX,oBAAA,KAAA,sBAAA,CAAA,WAAA;AAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;cA+Ba,OAAA;AAAA,cAEA,MAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwgBG,WAAA,CAAA;0BAAW,SAAA,CAAA,gBAAA;;+BAnPuC,oBAAA,KAAA,sBAAA,CAAA,WAAA;AAAA"}
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { n as router, r as version, t as createOwCli } from "./src-BLm6ei_p.mjs";
1
+ import { n as router, r as version, t as createOwCli } from "./src-BN5uTbLS.mjs";
2
2
 
3
3
  export { createOwCli, router, version };
@@ -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
- if (!verbose) s.start("Generating reference with AI...");
299
+ if (!verbose) s.start("Generating reference with OpenCode...");
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,14 +390,11 @@ 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;
380
- s.start("Generating reference with AI...");
397
+ s.start("Generating reference with OpenCode...");
381
398
  const qualifiedName = source.qualifiedName;
382
399
  const referenceRepoName = source.type === "remote" ? source.fullName : source.name;
383
400
  const result = await generateReferenceWithAI(repoPath, referenceRepoName, {
@@ -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 });
@@ -1852,6 +1876,20 @@ function showBanner() {
1852
1876
  for (const line of LOGO_LINES) console.log(`${OLIVE}${line}${RESET}`);
1853
1877
  console.log();
1854
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
+ }
1855
1893
  async function projectInitHandler(options = {}) {
1856
1894
  showBanner();
1857
1895
  p.intro("Scan your deps to install reference files and create a clone map for your agents.");
@@ -1995,28 +2033,23 @@ async function projectInitHandler(options = {}) {
1995
2033
  message: "Dry run complete"
1996
2034
  };
1997
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;
1998
2042
  const installed = [];
1999
2043
  const successfulMatches = [];
2000
2044
  let failedCount = 0;
2001
- const total = selected.length;
2002
- for (let i = 0; i < selected.length; i++) {
2003
- const match = selected[i];
2004
- if (!match.repo) continue;
2005
- const repo = match.repo;
2006
- const prefix = `${`[${i + 1}/${total}]`} ${match.dep}`;
2007
- const spinner = p.spinner();
2008
- spinner.start(`${prefix}: Starting...`);
2009
- try {
2010
- const pullResult = await pullHandler({
2011
- repo,
2012
- shallow: true,
2013
- force: options.generate,
2014
- verbose: false,
2015
- quiet: true,
2016
- skipConfirm: true,
2017
- onProgress: (msg) => spinner.message(`${prefix}: ${msg}`)
2018
- });
2019
- 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 {
2020
2053
  const referencePath = getReferencePath(repo);
2021
2054
  successfulMatches.push(match);
2022
2055
  installed.push({
@@ -2024,16 +2057,121 @@ async function projectInitHandler(options = {}) {
2024
2057
  reference: toReferenceFileName(repo),
2025
2058
  path: referencePath
2026
2059
  });
2027
- const source = pullResult.referenceSource === "remote" ? "downloaded" : "generated";
2028
- spinner.stop(`${prefix} ${pc.green("✓")} ${pc.dim(`(${source})`)}`);
2029
- } 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
+ }
2030
2112
  spinner.stop(`${prefix} ${pc.red("✗")} ${pc.red("failed")}`);
2031
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
+ };
2126
+ }
2127
+ }), concurrency);
2128
+ }
2129
+ if (generateGroup.length > 0) {
2130
+ p.log.step(`Generating ${pc.yellow(generateGroup.length)} references with OpenCode...`);
2131
+ let openCodeContext;
2132
+ try {
2133
+ if (generateGroup.length > 1) {
2134
+ p.log.info(pc.dim("Starting OpenCode server for batch generation..."));
2135
+ openCodeContext = await createOpenCodeContext({ onDebug: () => {} });
2032
2136
  }
2033
- } catch (error) {
2034
- const errMsg = error instanceof Error ? error.message : "Unknown error";
2035
- spinner.stop(`${prefix} ${pc.red("✗")} ${pc.dim(errMsg)}`);
2036
- failedCount++;
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();
2037
2175
  }
2038
2176
  }
2039
2177
  if (successfulMatches.length > 0) {
@@ -2334,12 +2472,11 @@ async function mapSearchHandler(options) {
2334
2472
 
2335
2473
  //#endregion
2336
2474
  //#region src/index.ts
2337
- const version = "0.3.4";
2475
+ const version = "0.3.6";
2338
2476
  const router = os.router({
2339
2477
  pull: os.input(z.object({
2340
2478
  repo: z.string().describe("repo").meta({ positional: true }),
2341
2479
  reference: z.string().optional().describe("Reference name to pull (defaults to owner-repo)").meta({ alias: "r" }),
2342
- shallow: z.boolean().default(false).describe("Use shallow clone (--depth 1)").meta({ negativeAlias: "full-history" }),
2343
2480
  sparse: z.boolean().default(false).describe("Use sparse checkout (only src/, lib/, packages/, docs/)"),
2344
2481
  branch: z.string().optional().describe("Branch to clone"),
2345
2482
  force: z.boolean().default(false).describe("Force re-generation").meta({ alias: "f" }),
@@ -2352,7 +2489,6 @@ const router = os.router({
2352
2489
  await pullHandler({
2353
2490
  repo: input.repo,
2354
2491
  reference: input.reference,
2355
- shallow: input.shallow,
2356
2492
  sparse: input.sparse,
2357
2493
  branch: input.branch,
2358
2494
  force: input.force,
@@ -2433,10 +2569,11 @@ const router = os.router({
2433
2569
  })).meta({ description: `Set a config value
2434
2570
 
2435
2571
  Valid keys:
2436
- repoRoot (string) Where to clone repos (e.g., ~/ow)
2437
- defaultShallow (boolean) Use shallow clone by default (true/false)
2438
- defaultModel (string) AI provider/model (e.g., anthropic/claude-sonnet-4-20250514)
2439
- 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 }) => {
2440
2577
  await configSetHandler({
2441
2578
  key: input.key,
2442
2579
  value: input.value
@@ -2444,7 +2581,7 @@ Valid keys:
2444
2581
  }),
2445
2582
  get: os.input(z.object({ key: z.string().describe("key").meta({ positional: true }) })).meta({ description: `Get a config value
2446
2583
 
2447
- Valid keys: repoRoot, defaultShallow, defaultModel, agents` }).handler(async ({ input }) => {
2584
+ Valid keys: repoRoot, defaultModel, maxCommitDistance, acceptUnknownDistance, agents` }).handler(async ({ input }) => {
2448
2585
  await configGetHandler({ key: input.key });
2449
2586
  }),
2450
2587
  reset: os.input(z.object({})).meta({ description: "Reset config to defaults" }).handler(async () => {
@@ -2476,9 +2613,10 @@ Valid keys: repoRoot, defaultShallow, defaultModel, agents` }).handler(async ({
2476
2613
  all: z.boolean().default(false).describe("Select all detected dependencies"),
2477
2614
  deps: z.string().optional().describe("Comma-separated deps to include (skip selection)"),
2478
2615
  skip: z.string().optional().describe("Comma-separated deps to exclude"),
2479
- generate: z.boolean().default(false).describe("Generate references for deps without existing ones").meta({ alias: "g" }),
2616
+ generate: z.boolean().default(false).describe("Force local generation for deps without existing refs").meta({ alias: "g" }),
2480
2617
  dryRun: z.boolean().default(false).describe("Show what would be done without doing it").meta({ alias: "d" }),
2481
- 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" })
2482
2620
  })).meta({
2483
2621
  description: "Scan manifest, install references, update AGENTS.md",
2484
2622
  default: true
@@ -2489,7 +2627,8 @@ Valid keys: repoRoot, defaultShallow, defaultModel, agents` }).handler(async ({
2489
2627
  skip: input.skip,
2490
2628
  generate: input.generate,
2491
2629
  dryRun: input.dryRun,
2492
- yes: input.yes
2630
+ yes: input.yes,
2631
+ concurrency: input.concurrency
2493
2632
  });
2494
2633
  }) }),
2495
2634
  map: os.router({
@@ -2540,14 +2679,12 @@ Valid keys: repoRoot, defaultShallow, defaultModel, agents` }).handler(async ({
2540
2679
  update: os.input(z.object({
2541
2680
  all: z.boolean().default(false).describe("Update all repos"),
2542
2681
  pattern: z.string().optional().describe("Filter by pattern"),
2543
- dryRun: z.boolean().default(false).describe("Show what would be updated").meta({ alias: "d" }),
2544
- 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" })
2545
2683
  })).meta({ description: "Update repos (git fetch + pull)" }).handler(async ({ input }) => {
2546
2684
  await repoUpdateHandler({
2547
2685
  all: input.all,
2548
2686
  pattern: input.pattern,
2549
- dryRun: input.dryRun,
2550
- unshallow: input.unshallow
2687
+ dryRun: input.dryRun
2551
2688
  });
2552
2689
  }),
2553
2690
  prune: os.input(z.object({
@@ -2634,4 +2771,4 @@ function createOwCli() {
2634
2771
 
2635
2772
  //#endregion
2636
2773
  export { router as n, version as r, createOwCli as t };
2637
- //# sourceMappingURL=src-BLm6ei_p.mjs.map
2774
+ //# sourceMappingURL=src-BN5uTbLS.mjs.map