resulgit 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +13 -8
  2. package/package.json +1 -1
  3. package/resulgit.js +156 -86
package/README.md CHANGED
@@ -37,10 +37,12 @@ resulgit <command> [options]
37
37
  - `resulgit repo head --repo <id> [--branch <name>]` - Get HEAD commit
38
38
  - `resulgit repo select` - Interactive repository selection
39
39
 
40
- ### Clone & Workspace
40
+ ### Clone & Initialize
41
41
 
42
- - `resulgit init [--dir <path>] [--repo <id>] [--server <url>] [--branch <name>] [--token <token>]` - Initialize a new repository
43
- - `resulgit clone --repo <id> --branch <name> [--dest <dir>]` - Clone a repository
42
+ - `resulgit init <name>` - Initialize a new repository (creates folder and remote repo automatically)
43
+ - `resulgit init <name> --dir <path>` - Initialize in a specific directory
44
+ - `resulgit clone <name>` - Clone a repository by name
45
+ - `resulgit clone --repo <name> --branch <branch>` - Clone with specific branch
44
46
  - `resulgit workspace set-root --path <dir>` - Set workspace root directory
45
47
 
46
48
  ### Branch Operations
@@ -63,6 +65,8 @@ resulgit <command> [options]
63
65
  ### Version Control
64
66
 
65
67
  - `resulgit commit --message <text>` - Create a commit
68
+ - `resulgit commit -m <text>` - Short form (same as above)
69
+ - `resulgit commit -a -m <text>` - Add all changes and commit
66
70
  - `resulgit push` - Push changes to remote
67
71
  - `resulgit pull` - Pull changes from remote
68
72
  - `resulgit merge --branch <name> [--squash] [--no-push]` - Merge branches
@@ -131,18 +135,19 @@ resulgit auth login --email user@example.com --password mypassword
131
135
  # List repositories
132
136
  resulgit repo list
133
137
 
134
- # Initialize a new repository
135
- resulgit init --repo 123 --branch main
138
+ # Initialize a new repository (creates folder + remote repo automatically)
139
+ resulgit init MyProject
136
140
 
137
- # Clone a repository
138
- resulgit clone --repo 123 --branch main
141
+ # Or clone an existing repository
142
+ resulgit clone MyProject
139
143
 
140
144
  # Check status
145
+ cd MyProject
141
146
  resulgit status
142
147
 
143
148
  # Create and commit changes
144
149
  resulgit add file.txt --content "Hello World"
145
- resulgit commit --message "Add file.txt"
150
+ resulgit commit -m "Add file.txt"
146
151
  resulgit push
147
152
 
148
153
  # Create a branch
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "resulgit",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "A powerful CLI tool for version control system operations - clone, commit, push, pull, merge, branch management, and more",
5
5
  "main": "resulgit.js",
6
6
  "bin": {
package/resulgit.js CHANGED
@@ -37,12 +37,20 @@ function parseArgs(argv) {
37
37
  const t = tokens[i]
38
38
  if (t.startsWith('--')) {
39
39
  const key = t.slice(2)
40
- const val = tokens[i + 1] && !tokens[i + 1].startsWith('--') ? tokens[++i] : 'true'
40
+ const val = tokens[i + 1] && !tokens[i + 1].startsWith('-') ? tokens[++i] : 'true'
41
+ opts[key] = val
42
+ } else if (t.startsWith('-') && t.length === 2) {
43
+ // Short flag like -m, -a
44
+ const key = t.slice(1)
45
+ const val = tokens[i + 1] && !tokens[i + 1].startsWith('-') ? tokens[++i] : 'true'
41
46
  opts[key] = val
42
47
  } else if (cmd.length < 2) {
43
48
  cmd.push(t)
44
49
  }
45
50
  }
51
+ // Map short flags to long flags
52
+ if (opts.m && !opts.message) { opts.message = opts.m; delete opts.m }
53
+ if (opts.a && !opts.all) { opts.all = opts.a; delete opts.a }
46
54
  return { cmd, opts }
47
55
  }
48
56
 
@@ -271,7 +279,7 @@ async function cmdClone(opts, cfg) {
271
279
  const server = getServer(opts, cfg)
272
280
  const token = getToken(opts, cfg)
273
281
  const repo = opts.repo
274
- const branch = opts.branch
282
+ const branch = opts.branch || 'main'
275
283
  let dest = opts.dest
276
284
  if (!repo || !branch) throw new Error('Missing --repo and --branch')
277
285
  const spinner = createSpinner('Initializing clone...', opts.json)
@@ -540,10 +548,10 @@ async function cmdDiff(opts) {
540
548
  const snap1 = await fetchSnapshotByCommit(server, meta.repoId, commit1, token)
541
549
  const snap2 = await fetchSnapshotByCommit(server, meta.repoId, commit2, token)
542
550
  const files = filePath ? [filePath] : Object.keys(new Set([...Object.keys(snap1.files), ...Object.keys(snap2.files)]))
543
-
551
+
544
552
  let added = 0, deleted = 0, modified = 0
545
553
  const stats = []
546
-
554
+
547
555
  for (const p of files) {
548
556
  const content1 = snap1.files[p] !== undefined ? String(snap1.files[p]) : null
549
557
  const content2 = snap2.files[p] !== undefined ? String(snap2.files[p]) : null
@@ -561,7 +569,7 @@ async function cmdDiff(opts) {
561
569
  const diff = Math.abs(lines2.length - lines1.length)
562
570
  if (showStat) stats.push({ path: p, added: lines2.length > lines1.length ? diff : 0, deleted: lines1.length > lines2.length ? diff : 0 })
563
571
  }
564
-
572
+
565
573
  if (!showStat) {
566
574
  if (opts.json === 'true') {
567
575
  print({ path: p, old: content1, new: content2 }, true)
@@ -585,7 +593,7 @@ async function cmdDiff(opts) {
585
593
  }
586
594
  }
587
595
  }
588
-
596
+
589
597
  if (showStat) {
590
598
  if (opts.json === 'true') {
591
599
  print({ added, deleted, modified, files: stats }, true)
@@ -607,7 +615,7 @@ async function cmdDiff(opts) {
607
615
  const parentSnap = parentId ? await fetchSnapshotByCommit(server, meta.repoId, parentId, token) : { files: {}, commitId: '' }
608
616
 
609
617
  const files = filePath ? [filePath] : Object.keys(new Set([...Object.keys(parentSnap.files), ...Object.keys(commitSnap.files)]))
610
-
618
+
611
619
  if (showStat) {
612
620
  let added = 0, deleted = 0, modified = 0
613
621
  const stats = []
@@ -640,7 +648,7 @@ async function cmdDiff(opts) {
640
648
  }
641
649
  return
642
650
  }
643
-
651
+
644
652
  for (const p of files) {
645
653
  const oldContent = parentSnap.files[p] !== undefined ? String(parentSnap.files[p]) : null
646
654
  const newContent = commitSnap.files[p] !== undefined ? String(commitSnap.files[p]) : null
@@ -726,7 +734,7 @@ async function cmdRm(opts) {
726
734
  const cfg = loadConfig()
727
735
  const server = getServer(opts, cfg) || meta.server
728
736
  const token = getToken(opts, cfg) || meta.token
729
-
737
+
730
738
  // Handle --cached flag (remove from index but keep file)
731
739
  if (opts.cached === 'true') {
732
740
  // Mark file for deletion in next commit but don't delete from filesystem
@@ -736,7 +744,7 @@ async function cmdRm(opts) {
736
744
  try {
737
745
  const s = await fs.promises.readFile(localPath, 'utf8')
738
746
  localMeta = JSON.parse(s)
739
- } catch {}
747
+ } catch { }
740
748
  if (!localMeta.removedFiles) localMeta.removedFiles = []
741
749
  if (!localMeta.removedFiles.includes(pathArg)) {
742
750
  localMeta.removedFiles.push(pathArg)
@@ -750,7 +758,7 @@ async function cmdRm(opts) {
750
758
  }
751
759
  return
752
760
  }
753
-
761
+
754
762
  const u = new URL(`/api/repositories/${meta.repoId}/files`, server)
755
763
  u.searchParams.set('branch', meta.branch)
756
764
  u.searchParams.set('path', pathArg)
@@ -776,7 +784,7 @@ async function cmdCommit(opts) {
776
784
  spinnerUpdate(spinner, 'Staging all changes...')
777
785
  await cmdAdd({ dir, all: 'true', json: opts.json })
778
786
  }
779
-
787
+
780
788
  // Handle --amend flag
781
789
  if (opts.amend === 'true') {
782
790
  spinnerUpdate(spinner, 'Amending last commit...')
@@ -823,7 +831,7 @@ async function cmdCommit(opts) {
823
831
  const local = await collectLocal(dir)
824
832
  const files = {}
825
833
  for (const [p, v] of Object.entries(local)) files[p] = v.content
826
-
834
+
827
835
  // Execute pre-commit hook
828
836
  spinnerUpdate(spinner, 'Running pre-commit hook...')
829
837
  try {
@@ -836,7 +844,7 @@ async function cmdCommit(opts) {
836
844
  if (err.message === 'pre-commit hook failed') throw err
837
845
  // Hook doesn't exist or other error, continue
838
846
  }
839
-
847
+
840
848
  localMeta.pendingCommit = { message, files, createdAt: Date.now() }
841
849
  // Clear conflicts if they were resolved
842
850
  if (localMeta.conflicts) {
@@ -844,12 +852,12 @@ async function cmdCommit(opts) {
844
852
  }
845
853
  await fs.promises.mkdir(metaDir, { recursive: true })
846
854
  await fs.promises.writeFile(localPath, JSON.stringify(localMeta, null, 2))
847
-
855
+
848
856
  // Execute post-commit hook
849
857
  try {
850
858
  await hooks.executeHook(dir, 'post-commit', { message, files: Object.keys(files) })
851
- } catch {}
852
-
859
+ } catch { }
860
+
853
861
  spinnerSuccess(spinner, `Staged changes for commit: "${message}"`)
854
862
  print({ pendingCommit: message }, opts.json === 'true')
855
863
  } catch (err) {
@@ -1395,7 +1403,7 @@ async function cmdPush(opts) {
1395
1403
  if (err.message === 'pre-push hook failed') throw err
1396
1404
  // Hook doesn't exist or other error, continue
1397
1405
  }
1398
-
1406
+
1399
1407
  const body = { message: localMeta.pendingCommit?.message || (opts.message || 'Push'), files: merged, branchName: remoteMeta.branch }
1400
1408
  spinnerUpdate(spinner, `Pushing to '${remoteMeta.branch}'...`)
1401
1409
  const url = new URL(`/api/repositories/${remoteMeta.repoId}/commits`, server).toString()
@@ -1404,12 +1412,12 @@ async function cmdPush(opts) {
1404
1412
  localMeta.baseFiles = merged
1405
1413
  localMeta.pendingCommit = null
1406
1414
  await fs.promises.writeFile(localPath, JSON.stringify(localMeta, null, 2))
1407
-
1415
+
1408
1416
  // Execute post-push hook
1409
1417
  try {
1410
1418
  await hooks.executeHook(dir, 'post-push', { branch: remoteMeta.branch, commitId: localMeta.baseCommitId })
1411
- } catch {}
1412
-
1419
+ } catch { }
1420
+
1413
1421
  spinnerSuccess(spinner, `Pushed to '${remoteMeta.branch}' (commit: ${(data.id || '').slice(0, 7)})`)
1414
1422
  print({ pushed: localMeta.baseCommitId }, opts.json === 'true')
1415
1423
  } catch (err) {
@@ -1758,7 +1766,7 @@ async function cmdBranch(sub, opts) {
1758
1766
  } catch { }
1759
1767
  process.stdout.write(color('Branches:\n', 'bold'))
1760
1768
  let list = (data.branches || []).map(b => ({ name: b.name, commitId: b.commitId || '', date: b.lastCommitDate || b.createdAt || '' }))
1761
-
1769
+
1762
1770
  // Handle --sort option
1763
1771
  if (opts.sort) {
1764
1772
  const sortBy = opts.sort.replace(/^-/, '') // Remove leading dash
@@ -1770,7 +1778,7 @@ async function cmdBranch(sub, opts) {
1770
1778
  })
1771
1779
  }
1772
1780
  }
1773
-
1781
+
1774
1782
  for (const b of list) {
1775
1783
  const isCur = b.name === current
1776
1784
  const mark = isCur ? color('*', 'green') : ' '
@@ -1838,7 +1846,7 @@ async function cmdSwitch(opts) {
1838
1846
  const cfg = loadConfig()
1839
1847
  const server = getServer(opts, cfg) || meta.server
1840
1848
  const token = getToken(opts, cfg) || meta.token
1841
-
1849
+
1842
1850
  // Handle -c flag (create and switch)
1843
1851
  if (create) {
1844
1852
  // Check if branch exists
@@ -1850,7 +1858,7 @@ async function cmdSwitch(opts) {
1850
1858
  await cmdBranch('create', { dir, name: branch, base: meta.branch, repo: meta.repoId, server, token })
1851
1859
  }
1852
1860
  }
1853
-
1861
+
1854
1862
  await pullToDir(meta.repoId, branch, dir, server, token)
1855
1863
  print({ repoId: meta.repoId, branch, dir }, opts.json === 'true')
1856
1864
  }
@@ -2297,14 +2305,67 @@ async function cmdReset(opts) {
2297
2305
  }
2298
2306
 
2299
2307
  async function cmdInit(opts) {
2300
- const dir = path.resolve(opts.dir || '.')
2301
2308
  const cfg = loadConfig()
2302
2309
  const server = getServer(opts, cfg)
2303
2310
  const token = getToken(opts, cfg)
2304
- const repo = opts.repo || ''
2305
- const branch = opts.branch || 'main'
2306
- const metaDir = path.join(dir, '.vcs-next')
2307
- const gitDir = path.join(dir, '.git')
2311
+ // Repository name must be a non‑empty string; validate format
2312
+ const repo = opts.repo ? validation.validateRepoName(opts.repo) : ''
2313
+ // Branch name defaults to 'main' if not supplied
2314
+ const branch = opts.branch ? opts.branch : 'main'
2315
+
2316
+ // Determine target directory:
2317
+ // - If --dir is specified, use that
2318
+ // - Otherwise, if repo name is provided, create folder with that name
2319
+ // - Fallback to current directory
2320
+ let targetDir
2321
+ if (opts.dir) {
2322
+ targetDir = path.resolve(opts.dir)
2323
+ } else if (repo) {
2324
+ targetDir = path.resolve(repo)
2325
+ } else {
2326
+ targetDir = path.resolve('.')
2327
+ }
2328
+
2329
+ // Create the target directory if it doesn't exist
2330
+ await fs.promises.mkdir(targetDir, { recursive: true })
2331
+
2332
+ const spinner = createSpinner(`Initializing repository${repo ? ` '${repo}'` : ''}...`, opts.json)
2333
+
2334
+ let repoId = repo
2335
+ // If a repo name is provided and we have server/token, create remote repo
2336
+ if (repo && server && token) {
2337
+ try {
2338
+ spinnerUpdate(spinner, 'Creating remote repository...')
2339
+ const createUrl = new URL('/api/repositories', server).toString()
2340
+ const createRes = await request('POST', createUrl, {
2341
+ name: repo,
2342
+ description: opts.description || '',
2343
+ visibility: opts.visibility || 'private',
2344
+ initializeWithReadme: false
2345
+ }, token)
2346
+ // Use the ID returned by the server (could be numeric or string)
2347
+ repoId = String(createRes.id || repo)
2348
+ spinnerUpdate(spinner, `Remote repository '${repo}' created`)
2349
+ } catch (err) {
2350
+ // If repo already exists, try to fetch its ID
2351
+ if (err.message && err.message.includes('409')) {
2352
+ spinnerUpdate(spinner, `Remote repository '${repo}' already exists, linking...`)
2353
+ try {
2354
+ const listUrl = new URL('/api/repositories', server).toString()
2355
+ const repos = await request('GET', listUrl, null, token)
2356
+ const found = (repos || []).find(r => r.name === repo)
2357
+ if (found) repoId = String(found.id)
2358
+ } catch { /* ignore */ }
2359
+ } else {
2360
+ // Other error - continue with local init only
2361
+ spinnerUpdate(spinner, `Could not create remote repo: ${err.message}. Proceeding with local init only.`)
2362
+ }
2363
+ }
2364
+ }
2365
+
2366
+ spinnerUpdate(spinner, 'Setting up local repository...')
2367
+ const metaDir = path.join(targetDir, '.vcs-next')
2368
+ const gitDir = path.join(targetDir, '.git')
2308
2369
  await fs.promises.mkdir(metaDir, { recursive: true })
2309
2370
  await fs.promises.mkdir(path.join(gitDir, 'refs', 'heads'), { recursive: true })
2310
2371
  await fs.promises.writeFile(path.join(gitDir, 'HEAD'), `ref: refs/heads/${branch}\n`, 'utf8')
@@ -2318,16 +2379,18 @@ async function cmdInit(opts) {
2318
2379
  '',
2319
2380
  '[vcs-next]',
2320
2381
  `\tserver = ${opts.server || server || ''}`,
2321
- `\trepoId = ${repo}`,
2382
+ `\trepoId = ${repoId}`,
2322
2383
  `\tbranch = ${branch}`,
2323
2384
  `\ttoken = ${opts.token || token || ''}`
2324
2385
  ].join('\n')
2325
2386
  await fs.promises.writeFile(path.join(gitDir, 'config'), gitConfig, 'utf8')
2326
- const remoteMeta = { repoId: repo, branch, commitId: '', server: opts.server || server || '', token: opts.token || token || '' }
2387
+ const remoteMeta = { repoId: repoId, branch, commitId: '', server: opts.server || server || '', token: opts.token || token || '' }
2327
2388
  await fs.promises.writeFile(path.join(metaDir, 'remote.json'), JSON.stringify(remoteMeta, null, 2))
2328
2389
  const localMeta = { baseCommitId: '', baseFiles: {}, pendingCommit: null }
2329
2390
  await fs.promises.writeFile(path.join(metaDir, 'local.json'), JSON.stringify(localMeta, null, 2))
2330
- print({ initialized: dir, branch }, opts.json === 'true')
2391
+
2392
+ spinnerSuccess(spinner, `Initialized repository in ${targetDir}`)
2393
+ print({ initialized: targetDir, branch, repoId: repoId }, opts.json === 'true')
2331
2394
  }
2332
2395
 
2333
2396
  async function cmdMv(opts) {
@@ -2602,38 +2665,38 @@ async function cmdBlame(opts) {
2602
2665
  const dir = path.resolve(opts.dir || '.')
2603
2666
  const filePath = opts.path
2604
2667
  if (!filePath) throw new errors.ValidationError('Missing --path', 'path')
2605
-
2668
+
2606
2669
  const validPath = validation.validateFilePath(filePath)
2607
2670
  const meta = readRemoteMeta(dir)
2608
2671
  const cfg = loadConfig()
2609
2672
  const server = getServer(opts, cfg) || meta.server
2610
2673
  const token = getToken(opts, cfg) || meta.token
2611
-
2674
+
2612
2675
  const spinner = createSpinner(`Getting blame for ${validPath}...`, opts.json)
2613
-
2676
+
2614
2677
  try {
2615
2678
  // Get file content
2616
2679
  const local = await collectLocal(dir)
2617
2680
  if (!local[validPath]) {
2618
2681
  throw new errors.FileSystemError(`File not found: ${validPath}`, validPath, 'read')
2619
2682
  }
2620
-
2683
+
2621
2684
  // Get commits
2622
2685
  const commitsUrl = new URL(`/api/repositories/${meta.repoId}/commits`, server)
2623
2686
  commitsUrl.searchParams.set('branch', meta.branch)
2624
2687
  const commitsRes = await fetch(commitsUrl.toString(), {
2625
2688
  headers: token ? { Authorization: `Bearer ${token}` } : {}
2626
2689
  })
2627
-
2690
+
2628
2691
  if (!commitsRes.ok) {
2629
2692
  throw new errors.NetworkError('Failed to fetch commits', commitsRes.status, commitsUrl.toString())
2630
2693
  }
2631
-
2694
+
2632
2695
  const commits = await commitsRes.json()
2633
2696
  const blameData = parseBlame(local[validPath].content, commits, validPath)
2634
-
2697
+
2635
2698
  spinnerSuccess(spinner, `Blame for ${validPath}`)
2636
-
2699
+
2637
2700
  if (opts.json === 'true') {
2638
2701
  print(formatBlameJson(blameData), false)
2639
2702
  } else {
@@ -2651,17 +2714,17 @@ async function cmdLog(opts) {
2651
2714
  const cfg = loadConfig()
2652
2715
  const server = getServer(opts, cfg) || meta.server
2653
2716
  const token = getToken(opts, cfg) || meta.token
2654
-
2717
+
2655
2718
  const spinner = createSpinner('Fetching commit history...', opts.json)
2656
-
2719
+
2657
2720
  try {
2658
2721
  const url = new URL(`/api/repositories/${meta.repoId}/commits`, server)
2659
2722
  if (opts.branch) url.searchParams.set('branch', opts.branch)
2660
2723
  else url.searchParams.set('branch', meta.branch)
2661
-
2724
+
2662
2725
  const data = await request('GET', url.toString(), null, token)
2663
2726
  let commits = Array.isArray(data) ? data : []
2664
-
2727
+
2665
2728
  // Filter by file path if provided
2666
2729
  const filePath = opts.path
2667
2730
  if (filePath) {
@@ -2674,7 +2737,7 @@ async function cmdLog(opts) {
2674
2737
  }
2675
2738
  commits = filteredCommits
2676
2739
  }
2677
-
2740
+
2678
2741
  // Filter by content pattern (-G flag)
2679
2742
  const pattern = opts.G || opts.pattern
2680
2743
  if (pattern) {
@@ -2684,7 +2747,7 @@ async function cmdLog(opts) {
2684
2747
  const commitSnap = await fetchSnapshotByCommit(server, meta.repoId, commit.id || commit._id, token)
2685
2748
  const parentId = (Array.isArray(commit.parents) && commit.parents[0]) || ''
2686
2749
  const parentSnap = parentId ? await fetchSnapshotByCommit(server, meta.repoId, parentId, token) : { files: {} }
2687
-
2750
+
2688
2751
  // Check if pattern matches in any file changed in this commit
2689
2752
  const allPaths = new Set([...Object.keys(parentSnap.files), ...Object.keys(commitSnap.files)])
2690
2753
  let matches = false
@@ -2702,9 +2765,9 @@ async function cmdLog(opts) {
2702
2765
  }
2703
2766
  commits = filteredCommits
2704
2767
  }
2705
-
2768
+
2706
2769
  spinnerSuccess(spinner, `Found ${commits.length} commits`)
2707
-
2770
+
2708
2771
  if (opts.json === 'true') {
2709
2772
  print(commits, true)
2710
2773
  } else if (opts.oneline === 'true') {
@@ -2733,7 +2796,7 @@ async function cmdLog(opts) {
2733
2796
 
2734
2797
  async function cmdHook(sub, opts) {
2735
2798
  const dir = path.resolve(opts.dir || '.')
2736
-
2799
+
2737
2800
  if (sub === 'list') {
2738
2801
  const hooksList = await hooks.listHooks(dir)
2739
2802
  if (opts.json === 'true') {
@@ -2751,18 +2814,18 @@ async function cmdHook(sub, opts) {
2751
2814
  }
2752
2815
  return
2753
2816
  }
2754
-
2817
+
2755
2818
  if (sub === 'install') {
2756
2819
  const hookName = opts.name
2757
2820
  if (!hookName) throw new Error('Missing --name')
2758
-
2821
+
2759
2822
  let script = opts.script
2760
2823
  if (!script && opts.sample === 'true') {
2761
2824
  script = hooks.SAMPLE_HOOKS[hookName]
2762
2825
  if (!script) throw new Error(`No sample available for ${hookName}`)
2763
2826
  }
2764
2827
  if (!script) throw new Error('Missing --script or use --sample')
2765
-
2828
+
2766
2829
  const result = await hooks.installHook(dir, hookName, script)
2767
2830
  print(result, opts.json === 'true')
2768
2831
  if (opts.json !== 'true') {
@@ -2770,20 +2833,20 @@ async function cmdHook(sub, opts) {
2770
2833
  }
2771
2834
  return
2772
2835
  }
2773
-
2836
+
2774
2837
  if (sub === 'remove') {
2775
2838
  const hookName = opts.name
2776
2839
  if (!hookName) throw new Error('Missing --name')
2777
-
2840
+
2778
2841
  const result = await hooks.removeHook(dir, hookName)
2779
2842
  print(result, opts.json === 'true')
2780
2843
  return
2781
2844
  }
2782
-
2845
+
2783
2846
  if (sub === 'show') {
2784
2847
  const hookName = opts.name
2785
2848
  if (!hookName) throw new Error('Missing --name')
2786
-
2849
+
2787
2850
  const result = await hooks.readHook(dir, hookName)
2788
2851
  if (result.content) {
2789
2852
  process.stdout.write(result.content + '\n')
@@ -2792,7 +2855,7 @@ async function cmdHook(sub, opts) {
2792
2855
  }
2793
2856
  return
2794
2857
  }
2795
-
2858
+
2796
2859
  throw new Error('Unknown hook subcommand. Use: list, install, remove, show')
2797
2860
  }
2798
2861
 
@@ -2800,13 +2863,13 @@ async function cmdGrep(opts) {
2800
2863
  const dir = path.resolve(opts.dir || '.')
2801
2864
  const pattern = opts.pattern || opts.p || ''
2802
2865
  if (!pattern) throw new Error('Missing --pattern or -p')
2803
-
2866
+
2804
2867
  const meta = readRemoteMeta(dir)
2805
2868
  const local = await collectLocal(dir)
2806
2869
  const results = []
2807
-
2870
+
2808
2871
  const regex = new RegExp(pattern, opts.ignoreCase === 'true' ? 'i' : '')
2809
-
2872
+
2810
2873
  for (const [filePath, fileData] of Object.entries(local)) {
2811
2874
  const lines = fileData.content.split(/\r?\n/)
2812
2875
  for (let i = 0; i < lines.length; i++) {
@@ -2819,7 +2882,7 @@ async function cmdGrep(opts) {
2819
2882
  }
2820
2883
  }
2821
2884
  }
2822
-
2885
+
2823
2886
  if (opts.json === 'true') {
2824
2887
  print(results, true)
2825
2888
  } else {
@@ -2836,10 +2899,10 @@ async function cmdLsFiles(opts) {
2836
2899
  const cfg = loadConfig()
2837
2900
  const server = getServer(opts, cfg) || meta.server
2838
2901
  const token = getToken(opts, cfg) || meta.token
2839
-
2902
+
2840
2903
  const remote = await fetchRemoteFilesMap(server, meta.repoId, meta.branch, token)
2841
2904
  const files = Object.keys(remote.map)
2842
-
2905
+
2843
2906
  if (opts.json === 'true') {
2844
2907
  print(files.map(f => ({ path: f })), true)
2845
2908
  } else {
@@ -2853,13 +2916,13 @@ async function cmdReflog(opts) {
2853
2916
  const dir = path.resolve(opts.dir || '.')
2854
2917
  const metaDir = path.join(dir, '.vcs-next')
2855
2918
  const reflogPath = path.join(metaDir, 'reflog.json')
2856
-
2919
+
2857
2920
  let reflog = []
2858
2921
  try {
2859
2922
  const content = await fs.promises.readFile(reflogPath, 'utf8')
2860
2923
  reflog = JSON.parse(content)
2861
- } catch {}
2862
-
2924
+ } catch { }
2925
+
2863
2926
  if (opts.json === 'true') {
2864
2927
  print(reflog, true)
2865
2928
  } else {
@@ -2878,14 +2941,14 @@ async function cmdCatFile(opts) {
2878
2941
  const dir = path.resolve(opts.dir || '.')
2879
2942
  const type = opts.type || ''
2880
2943
  const object = opts.object || ''
2881
-
2944
+
2882
2945
  if (!type || !object) throw new Error('Missing --type and --object')
2883
-
2946
+
2884
2947
  const meta = readRemoteMeta(dir)
2885
2948
  const cfg = loadConfig()
2886
2949
  const server = getServer(opts, cfg) || meta.server
2887
2950
  const token = getToken(opts, cfg) || meta.token
2888
-
2951
+
2889
2952
  if (type === 'blob') {
2890
2953
  const snap = await fetchSnapshotByCommit(server, meta.repoId, object, token)
2891
2954
  const filePath = opts.path || ''
@@ -2912,12 +2975,12 @@ async function cmdCatFile(opts) {
2912
2975
  async function cmdRevParse(opts) {
2913
2976
  const dir = path.resolve(opts.dir || '.')
2914
2977
  const rev = opts.rev || 'HEAD'
2915
-
2978
+
2916
2979
  const meta = readRemoteMeta(dir)
2917
2980
  const cfg = loadConfig()
2918
2981
  const server = getServer(opts, cfg) || meta.server
2919
2982
  const token = getToken(opts, cfg) || meta.token
2920
-
2983
+
2921
2984
  if (rev === 'HEAD') {
2922
2985
  const info = await request('GET', new URL(`/api/repositories/${meta.repoId}/branches`, server).toString(), null, token)
2923
2986
  const found = (info.branches || []).find(b => b.name === meta.branch)
@@ -2938,20 +3001,20 @@ async function cmdRevParse(opts) {
2938
3001
  async function cmdDescribe(opts) {
2939
3002
  const dir = path.resolve(opts.dir || '.')
2940
3003
  const commitId = opts.commit || 'HEAD'
2941
-
3004
+
2942
3005
  const meta = readRemoteMeta(dir)
2943
3006
  const cfg = loadConfig()
2944
3007
  const server = getServer(opts, cfg) || meta.server
2945
3008
  const token = getToken(opts, cfg) || meta.token
2946
-
3009
+
2947
3010
  // Get tags
2948
3011
  const tagsUrl = new URL(`/api/repositories/${meta.repoId}/tags`, server)
2949
3012
  const tags = await request('GET', tagsUrl.toString(), null, token)
2950
3013
  const tagsList = Array.isArray(tags) ? tags : []
2951
-
3014
+
2952
3015
  // Find nearest tag (simplified - just find any tag)
2953
3016
  const nearestTag = tagsList[0]
2954
-
3017
+
2955
3018
  if (nearestTag) {
2956
3019
  const desc = `${nearestTag.name}-0-g${commitId.slice(0, 7)}`
2957
3020
  print(opts.json === 'true' ? { tag: nearestTag.name, commitId, describe: desc } : desc, opts.json === 'true')
@@ -2966,16 +3029,16 @@ async function cmdShortlog(opts) {
2966
3029
  const cfg = loadConfig()
2967
3030
  const server = getServer(opts, cfg) || meta.server
2968
3031
  const token = getToken(opts, cfg) || meta.token
2969
-
3032
+
2970
3033
  const url = new URL(`/api/repositories/${meta.repoId}/commits`, server)
2971
3034
  if (opts.branch) url.searchParams.set('branch', opts.branch)
2972
3035
  else url.searchParams.set('branch', meta.branch)
2973
-
3036
+
2974
3037
  const commits = await request('GET', url.toString(), null, token)
2975
3038
  const commitsList = Array.isArray(commits) ? commits : []
2976
-
3039
+
2977
3040
  const stats = generateCommitStats(commitsList)
2978
-
3041
+
2979
3042
  if (opts.json === 'true') {
2980
3043
  print(stats, true)
2981
3044
  } else {
@@ -3044,8 +3107,8 @@ function help() {
3044
3107
  ' >>>>>>> incoming (incoming changes)',
3045
3108
  ' Resolve conflicts manually, then commit and push. Push is blocked until conflicts are resolved.',
3046
3109
  ' pr list|create|merge [--dir <path>] [--title <t>] [--id <id>] [--source <branch>] [--target <branch>]',
3047
- ' clone <url> [--dest <dir>] | --repo <id> --branch <name> [--dest <dir>] [--server <url>] [--token <token>]',
3048
- ' init [--dir <path>] [--repo <id>] [--server <url>] [--branch <name>] [--token <tok>]',
3110
+ ' clone <url> [--dest <dir>] | --repo <name> --branch <name> [--dest <dir>] [--server <url>] [--token <token>]',
3111
+ ' init [--dir <path>] [--repo <name>] [--server <url>] [--branch <name>] [--token <tok>]',
3049
3112
  ' remote show|set-url|set-token [--dir <path>] [--server <url>] [--token <tok>]',
3050
3113
  ' config list|get|set [--key <k>] [--value <v>]',
3051
3114
  ' clean [--dir <path>] [--force]',
@@ -3078,13 +3141,20 @@ async function main() {
3078
3141
  return
3079
3142
  }
3080
3143
  if (cmd[0] === 'clone') {
3081
- if (!opts.url && cmd[1] && !cmd[1].startsWith('--')) opts.url = cmd[1]
3144
+ const arg = cmd[1];
3145
+ if (arg && !arg.startsWith('--')) {
3146
+ if (arg.includes('://')) {
3147
+ opts.url = arg;
3148
+ } else {
3149
+ opts.repo = arg;
3150
+ }
3151
+ }
3082
3152
  if (opts.url) {
3083
- await cmdCloneFromUrl(opts, cfg)
3153
+ await cmdCloneFromUrl(opts, cfg);
3084
3154
  } else {
3085
- await cmdClone(opts, cfg)
3155
+ await cmdClone(opts, cfg);
3086
3156
  }
3087
- return
3157
+ return;
3088
3158
  }
3089
3159
  if (cmd[0] === 'status') {
3090
3160
  await cmdStatus(opts)