microgit-cli 0.0.0-stage → 0.1.1

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 (42) hide show
  1. package/README.md +257 -2
  2. package/package.json +26 -4
  3. package/src/api/index.js +111 -0
  4. package/src/api/index.js.map +1 -0
  5. package/src/cli/index.js +32 -0
  6. package/src/cli/index.js.map +1 -0
  7. package/src/cli/repl.js +312 -0
  8. package/src/cli/repl.js.map +1 -0
  9. package/src/commands/auth.js +305 -0
  10. package/src/commands/auth.js.map +1 -0
  11. package/src/commands/config.js +91 -0
  12. package/src/commands/config.js.map +1 -0
  13. package/src/commands/git.js +159 -0
  14. package/src/commands/git.js.map +1 -0
  15. package/src/commands/registry.js +139 -0
  16. package/src/commands/registry.js.map +1 -0
  17. package/src/commands/repos.js +256 -0
  18. package/src/commands/repos.js.map +1 -0
  19. package/src/config/index.js +124 -0
  20. package/src/config/index.js.map +1 -0
  21. package/src/config/settings.js +34 -0
  22. package/src/config/settings.js.map +1 -0
  23. package/src/error/index.js +14 -0
  24. package/src/error/index.js.map +1 -0
  25. package/src/git/index.js +163 -0
  26. package/src/git/index.js.map +1 -0
  27. package/src/help/index.js +52 -0
  28. package/src/help/index.js.map +1 -0
  29. package/src/index.js +9 -0
  30. package/src/index.js.map +1 -0
  31. package/src/theme/banner.js +44 -0
  32. package/src/theme/banner.js.map +1 -0
  33. package/src/theme/index.js +5 -0
  34. package/src/theme/index.js.map +1 -0
  35. package/src/theme/logo.js +36 -0
  36. package/src/theme/logo.js.map +1 -0
  37. package/src/theme/palette.js +32 -0
  38. package/src/theme/palette.js.map +1 -0
  39. package/src/ui/index.js +100 -0
  40. package/src/ui/index.js.map +1 -0
  41. package/src/util/index.js +30 -0
  42. package/src/util/index.js.map +1 -0
@@ -0,0 +1,91 @@
1
+ import { clearStoredConfig, configFilePath, loadConfig, normalizeUrl, readStoredConfig, writeStoredConfig, } from '../config/index.js';
2
+ import { BIN } from '../config/settings.js';
3
+ import { fail, formatDate, info, ok, printJson } from '../ui/index.js';
4
+ function mask(value) {
5
+ if (value.length <= 8)
6
+ return 'saved';
7
+ return `${value.slice(0, 4)}...${value.slice(-4)}`;
8
+ }
9
+ export async function configCommand(argv) {
10
+ const json = argv.includes('--json');
11
+ const args = argv.filter((arg) => arg !== '--json');
12
+ const [action, key, ...rest] = args;
13
+ if (!action || action === 'show' || action === '--help' || action === '-h') {
14
+ showConfig(json);
15
+ return;
16
+ }
17
+ if (action === 'path') {
18
+ console.log(configFilePath());
19
+ return;
20
+ }
21
+ if (action === 'clear') {
22
+ clearStoredConfig();
23
+ ok(`Removed ${configFilePath()}`);
24
+ return;
25
+ }
26
+ if (action === 'set') {
27
+ const name = key?.trim();
28
+ const value = rest.join(' ').trim();
29
+ if (!name || value === '') {
30
+ fail(`Usage: ${BIN} config set <url|username|token|password> <value>`);
31
+ }
32
+ const stored = readStoredConfig();
33
+ const next = { ...stored };
34
+ if (name === 'url')
35
+ next.url = normalizeUrl(value);
36
+ else if (name === 'username')
37
+ next.username = value;
38
+ else if (name === 'token')
39
+ next.token = value;
40
+ else if (name === 'password')
41
+ next.password = value;
42
+ else
43
+ fail(`Unknown config key '${name}'. Use url, username, token or password.`);
44
+ writeStoredConfig(next);
45
+ ok(`Set ${name}.`);
46
+ if (name === 'password')
47
+ info('The password is stored in plain text in the config file.');
48
+ return;
49
+ }
50
+ if (action === 'unset') {
51
+ const name = key?.trim();
52
+ if (!name)
53
+ fail(`Usage: ${BIN} config unset <token|password|username|expiresAt>`);
54
+ const stored = readStoredConfig();
55
+ const next = { ...stored };
56
+ if (name === 'token' || name === 'password' || name === 'username' || name === 'expiresAt') {
57
+ delete next[name];
58
+ }
59
+ else {
60
+ fail(`Unknown config key '${name}'.`);
61
+ }
62
+ writeStoredConfig(next);
63
+ ok(`Unset ${name}.`);
64
+ return;
65
+ }
66
+ fail(`Unknown config action '${action}'. Try: ${BIN} config`);
67
+ }
68
+ function showConfig(json) {
69
+ const config = loadConfig();
70
+ const stored = readStoredConfig();
71
+ const token = config.token;
72
+ if (json) {
73
+ printJson({
74
+ configPath: config.configPath,
75
+ url: config.url,
76
+ username: config.username ?? null,
77
+ token: token ? 'saved' : null,
78
+ expiresAt: config.expiresAt ?? null,
79
+ password: config.password ? 'saved' : null,
80
+ });
81
+ return;
82
+ }
83
+ const row = (label, value) => ` ${label.padEnd(12)} ${value}`;
84
+ console.log(row('Config file', config.configPath));
85
+ console.log(row('Server', config.url));
86
+ console.log(row('Username', config.username ?? '-'));
87
+ console.log(row('Token', token ? mask(token) : '-'));
88
+ console.log(row('Expires', formatDate(config.expiresAt)));
89
+ console.log(row('Password', stored.password ? 'saved (plain text)' : '-'));
90
+ }
91
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,159 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { parseArgs } from 'node:util';
4
+ import { loadConfig } from '../config/index.js';
5
+ import { ensureIdentity, git, gitChecked, pullRepo, pushRepo, requireGitRepo } from '../git/index.js';
6
+ import { BIN } from '../config/settings.js';
7
+ import { ask, fail, info, ok } from '../ui/index.js';
8
+ import { text, trimmed } from '../util/index.js';
9
+ export async function initCommand(argv) {
10
+ const { values, positionals } = parseArgs({
11
+ args: argv,
12
+ allowPositionals: true,
13
+ options: {
14
+ dir: { type: 'string' },
15
+ help: { type: 'boolean', short: 'h' },
16
+ },
17
+ });
18
+ if (values.help) {
19
+ console.log(`Usage: ${BIN} init [directory] [options]
20
+
21
+ Create a project folder, run 'git init' in it and add an empty README.md.
22
+ Nothing is committed or pushed, and no server repository is created.
23
+
24
+ Options:
25
+ --dir <path> Directory to use (default: the given name, else this folder)
26
+ -h, --help Show this help
27
+
28
+ Examples:
29
+ ${BIN} init app
30
+ ${BIN} init # initialise the current folder
31
+ ${BIN} init --dir ./projects/app`);
32
+ return;
33
+ }
34
+ const name = trimmed(positionals[0]);
35
+ const target = path.resolve(trimmed(values.dir) ?? name ?? '.');
36
+ const label = path.basename(target) === '' ? target : path.basename(target);
37
+ if (fs.existsSync(path.join(target, '.git'))) {
38
+ fail(`Already a git repository: ${target}`);
39
+ }
40
+ fs.mkdirSync(target, { recursive: true });
41
+ await gitChecked(['init', '-b', 'main'], { cwd: target, inherit: false });
42
+ let username;
43
+ try {
44
+ username = loadConfig().username;
45
+ }
46
+ catch {
47
+ username = undefined;
48
+ }
49
+ if (username)
50
+ await ensureIdentity(target, username);
51
+ const readme = path.join(target, 'README.md');
52
+ if (!fs.existsSync(readme))
53
+ fs.writeFileSync(readme, '', 'utf8');
54
+ ok(`Initialised ${label} with git and an empty README.md`);
55
+ info(`Next: cd ${label} · ${BIN} status`);
56
+ }
57
+ export async function statusCommand(argv) {
58
+ if (argv.includes('--help') || argv.includes('-h')) {
59
+ console.log(`Usage: ${BIN} status`);
60
+ return;
61
+ }
62
+ await requireGitRepo();
63
+ await gitChecked(['status', '--short', '--branch']);
64
+ }
65
+ export async function commitCommand(argv) {
66
+ const { values } = parseArgs({
67
+ args: argv,
68
+ allowPositionals: true,
69
+ options: {
70
+ message: { type: 'string', short: 'm' },
71
+ 'no-add': { type: 'boolean' },
72
+ 'no-push': { type: 'boolean' },
73
+ 'allow-empty': { type: 'boolean' },
74
+ url: { type: 'string' },
75
+ help: { type: 'boolean', short: 'h' },
76
+ },
77
+ });
78
+ if (values.help) {
79
+ console.log(`Usage: ${BIN} commit -m "message" [options]
80
+
81
+ Stage every change, commit it and push the current branch.
82
+
83
+ Options:
84
+ -m, --message <text> Commit message (asked interactively when omitted)
85
+ --no-add Do not run 'git add -A' first
86
+ --no-push Commit only, do not push
87
+ --allow-empty Allow a commit without staged changes
88
+ --url <url> Server URL (for credentials)
89
+ -h, --help Show this help
90
+
91
+ Example:
92
+ ${BIN} commit -m "Add login page"
93
+ ${BIN} commit -m "WIP" --no-push`);
94
+ return;
95
+ }
96
+ await requireGitRepo();
97
+ const cwd = process.cwd();
98
+ let message = text(values.message)?.trim();
99
+ if (!message) {
100
+ if (!process.stdin.isTTY)
101
+ fail('Commit message is required. Use -m "message".');
102
+ message = await ask('Commit message: ');
103
+ }
104
+ if (!message)
105
+ fail('Commit message cannot be empty.');
106
+ const config = loadConfig(text(values.url));
107
+ if (!values['no-add']) {
108
+ await gitChecked(['add', '-A'], { cwd, inherit: false });
109
+ }
110
+ const diff = await git(['diff', '--cached', '--quiet'], { cwd });
111
+ if (diff.code > 1) {
112
+ fail(diff.stderr.trim() || 'Could not inspect the staged changes.');
113
+ }
114
+ let committed = false;
115
+ if (diff.code === 0 && !values['allow-empty']) {
116
+ info('Nothing to commit.');
117
+ }
118
+ else {
119
+ const commitArgs = ['commit', '-m', message];
120
+ if (values['allow-empty'])
121
+ commitArgs.push('--allow-empty');
122
+ await gitChecked(commitArgs, { cwd });
123
+ committed = true;
124
+ }
125
+ if (values['no-push']) {
126
+ if (committed)
127
+ ok('Committed.');
128
+ return;
129
+ }
130
+ await pushRepo(config, [], cwd);
131
+ if (committed)
132
+ ok('Committed and pushed.');
133
+ else
134
+ ok('Pushed.');
135
+ }
136
+ export async function pushCommand(argv) {
137
+ if (argv.includes('--help') || argv.includes('-h')) {
138
+ console.log(`Usage: ${BIN} push [git push arguments]
139
+
140
+ Push the current branch with the saved credentials. Extra arguments are passed
141
+ to git, for example: ${BIN} push --force-with-lease`);
142
+ return;
143
+ }
144
+ const config = loadConfig();
145
+ await pushRepo(config, argv);
146
+ ok('Pushed.');
147
+ }
148
+ export async function pullCommand(argv) {
149
+ if (argv.includes('--help') || argv.includes('-h')) {
150
+ console.log(`Usage: ${BIN} pull [git pull arguments]
151
+
152
+ Pull from origin with the saved credentials. Extra arguments are passed to git.`);
153
+ return;
154
+ }
155
+ const config = loadConfig();
156
+ await pullRepo(config, argv);
157
+ ok('Pulled.');
158
+ }
159
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/commands/git.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,139 @@
1
+ import { loginCommand, logoutCommand, registerCommand, whoamiCommand } from './auth.js';
2
+ import { configCommand } from './config.js';
3
+ import { commitCommand, initCommand, pullCommand, pushCommand, statusCommand } from './git.js';
4
+ import { cloneCommand, deleteCommand, listCommand, newCommand } from './repos.js';
5
+ import { CliError } from '../error/index.js';
6
+ /**
7
+ * One table drives the entry point, `help` and the shell's tab completion, so a
8
+ * new command only has to be added here.
9
+ */
10
+ export const COMMANDS = [
11
+ {
12
+ name: 'login',
13
+ group: 'Account',
14
+ summary: 'Sign in and store the API session',
15
+ flags: ['--url', '--username', '--password', '--save-password', '--forget-password', '--json', '--help'],
16
+ run: loginCommand,
17
+ },
18
+ {
19
+ name: 'register',
20
+ group: 'Account',
21
+ summary: 'Create an account and sign in',
22
+ flags: ['--url', '--username', '--password', '--save-password', '--forget-password', '--json', '--help'],
23
+ run: registerCommand,
24
+ },
25
+ {
26
+ name: 'logout',
27
+ group: 'Account',
28
+ summary: 'Revoke the session and clear local credentials',
29
+ flags: ['--url', '--json', '--help'],
30
+ run: logoutCommand,
31
+ },
32
+ {
33
+ name: 'whoami',
34
+ aliases: ['me'],
35
+ group: 'Account',
36
+ summary: 'Show the signed-in account',
37
+ flags: ['--url', '--json', '--help'],
38
+ run: whoamiCommand,
39
+ },
40
+ {
41
+ name: 'new',
42
+ aliases: ['create'],
43
+ group: 'Repositories',
44
+ summary: 'Create a repository (--private, --clone, --init)',
45
+ flags: ['--url', '--private', '--public', '--clone', '--init', '--dir', '--json', '--help'],
46
+ run: newCommand,
47
+ },
48
+ {
49
+ name: 'list',
50
+ aliases: ['ls'],
51
+ group: 'Repositories',
52
+ summary: 'List repositories (--owner, --public, --private)',
53
+ flags: ['--url', '--owner', '--name', '--private', '--public', '--page', '--limit', '--json', '--help'],
54
+ run: listCommand,
55
+ },
56
+ {
57
+ name: 'delete',
58
+ aliases: ['rm'],
59
+ group: 'Repositories',
60
+ summary: 'Delete a repository (owner/name for another account)',
61
+ flags: ['--url', '--yes', '--json', '--help'],
62
+ run: deleteCommand,
63
+ },
64
+ {
65
+ name: 'clone',
66
+ group: 'Repositories',
67
+ summary: 'Clone a repository and use the server’s clone URL',
68
+ flags: ['--url', '--dir', '--json', '--help'],
69
+ run: cloneCommand,
70
+ },
71
+ {
72
+ name: 'init',
73
+ group: 'Git',
74
+ summary: 'Start a local repository: folder, .git and an empty README',
75
+ flags: ['--dir', '--help'],
76
+ run: initCommand,
77
+ },
78
+ {
79
+ name: 'status',
80
+ group: 'Git',
81
+ summary: 'Short git status of the current repository',
82
+ flags: ['--help'],
83
+ run: statusCommand,
84
+ },
85
+ {
86
+ name: 'commit',
87
+ group: 'Git',
88
+ summary: 'Stage everything, commit and push',
89
+ flags: ['--url', '--message', '--no-add', '--no-push', '--allow-empty', '--help'],
90
+ run: commitCommand,
91
+ },
92
+ {
93
+ name: 'push',
94
+ group: 'Git',
95
+ summary: 'Push with the saved credentials',
96
+ flags: ['--help'],
97
+ run: pushCommand,
98
+ },
99
+ {
100
+ name: 'pull',
101
+ group: 'Git',
102
+ summary: 'Pull with the saved credentials',
103
+ flags: ['--help'],
104
+ run: pullCommand,
105
+ },
106
+ {
107
+ name: 'config',
108
+ group: 'Config',
109
+ summary: 'Show or edit the config file and session',
110
+ flags: ['--json', '--help'],
111
+ run: configCommand,
112
+ },
113
+ ];
114
+ export const GROUPS = ['Account', 'Repositories', 'Git', 'Config'];
115
+ /** Commands the interactive shell handles itself. */
116
+ export const SHELL_BUILTINS = ['clear', 'exit', 'help'];
117
+ /** Look a command up by name or alias, case-insensitively. */
118
+ export function findCommand(name) {
119
+ const needle = name.trim().toLowerCase();
120
+ if (needle === '')
121
+ return undefined;
122
+ return COMMANDS.find((command) => command.name === needle || command.aliases?.includes(needle) === true);
123
+ }
124
+ /** Every accepted command word, sorted, for completion. */
125
+ export function commandNames() {
126
+ const names = COMMANDS.flatMap((command) => [command.name, ...(command.aliases ?? [])]);
127
+ return [...new Set(names)].sort();
128
+ }
129
+ /** Run one command line; unknown names fail like the one-shot CLI does. */
130
+ export async function dispatch(argv) {
131
+ const [name, ...rest] = argv;
132
+ const command = name === undefined ? undefined : findCommand(name);
133
+ if (command === undefined) {
134
+ const label = name === undefined ? '' : ` '${name}'`;
135
+ throw new CliError(`Unknown command${label}. Type \`help\` to list the commands.`);
136
+ }
137
+ await command.run(rest);
138
+ }
139
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/commands/registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,256 @@
1
+ import { parseArgs } from 'node:util';
2
+ import { ApiClient } from '../api/index.js';
3
+ import { loadConfig, requireAuth } from '../config/index.js';
4
+ import { cloneRepo, initRepo } from '../git/index.js';
5
+ import { BIN } from '../config/settings.js';
6
+ import { ask, confirm, cyan, dim, fail, faint, info, ok, printJson, table } from '../ui/index.js';
7
+ import { parseTarget, positiveInt, text, trimmed } from '../util/index.js';
8
+ /** Ask public/private when the flag is not given; Enter means public. */
9
+ async function askVisibility() {
10
+ const answer = (await ask(`Visibility ${dim('[public/private]')} ${faint('(public)')}: `))
11
+ .toLowerCase()
12
+ .trim();
13
+ if (answer === '')
14
+ return false;
15
+ if (answer === 'private' || answer === 'priv' || answer === 'p')
16
+ return true;
17
+ if (answer === 'public' || answer === 'pub')
18
+ return false;
19
+ fail(`Unknown visibility '${answer}'. Use public or private.`);
20
+ }
21
+ export async function listCommand(argv) {
22
+ const { values } = parseArgs({
23
+ args: argv,
24
+ allowPositionals: true,
25
+ options: {
26
+ url: { type: 'string' },
27
+ owner: { type: 'string' },
28
+ name: { type: 'string' },
29
+ private: { type: 'boolean' },
30
+ public: { type: 'boolean' },
31
+ page: { type: 'string' },
32
+ limit: { type: 'string' },
33
+ json: { type: 'boolean' },
34
+ help: { type: 'boolean', short: 'h' },
35
+ },
36
+ });
37
+ if (values.help) {
38
+ console.log(`Usage: ${BIN} list [options]
39
+
40
+ Options:
41
+ --owner <name> Only repositories owned by this account
42
+ --name <name> Filter by repository name
43
+ --private Only private repositories
44
+ --public Only public repositories
45
+ --page <n> Page number (default 1)
46
+ --limit <n> Page size (default 50, max 200)
47
+ --json Print the raw API response
48
+ --url <url> Server URL
49
+ -h, --help Show this help`);
50
+ return;
51
+ }
52
+ if (values.private && values.public)
53
+ fail('Use either --private or --public, not both.');
54
+ const visibility = values.private ? true : values.public ? false : null;
55
+ const config = loadConfig(text(values.url));
56
+ const api = new ApiClient(config.url, config.token);
57
+ const result = await api.listRepos({
58
+ owner: trimmed(values.owner),
59
+ name: trimmed(values.name),
60
+ private: visibility,
61
+ page: positiveInt(values.page, '--page', 1),
62
+ limit: positiveInt(values.limit, '--limit', 50),
63
+ });
64
+ if (values.json) {
65
+ printJson(result);
66
+ return;
67
+ }
68
+ if (result.repositories.length === 0) {
69
+ info('No repositories found.');
70
+ return;
71
+ }
72
+ const rows = result.repositories.map((repo) => [
73
+ `${repo.owner}/${repo.name}`,
74
+ repo.private ? 'private' : 'public',
75
+ repo.cloneUrl,
76
+ ]);
77
+ console.log(table(['REPOSITORY', 'VISIBILITY', 'CLONE URL'], rows));
78
+ info(`${result.total} ${result.total === 1 ? 'repository' : 'repositories'} · ` +
79
+ `page ${result.page}/${Math.max(result.pages, 1)}`);
80
+ }
81
+ export async function newCommand(argv) {
82
+ const { values, positionals } = parseArgs({
83
+ args: argv,
84
+ allowPositionals: true,
85
+ options: {
86
+ url: { type: 'string' },
87
+ private: { type: 'boolean' },
88
+ public: { type: 'boolean' },
89
+ clone: { type: 'boolean' },
90
+ init: { type: 'boolean' },
91
+ dir: { type: 'string' },
92
+ json: { type: 'boolean' },
93
+ help: { type: 'boolean', short: 'h' },
94
+ },
95
+ });
96
+ if (values.help) {
97
+ console.log(`Usage: ${BIN} new <name> [options]
98
+
99
+ Create a repository for the signed-in account. Without --private or --public
100
+ the CLI asks for the visibility (Enter creates a public repository).
101
+
102
+ Options:
103
+ --private Create it private
104
+ --public Create it public
105
+ --clone Clone it into ./<name> (or --dir) afterwards
106
+ --init Create ./<name>, make a README, first commit and push
107
+ --dir <path> Directory for --clone/--init (default: repository name)
108
+ --json Print the API response as JSON
109
+ --url <url> Server URL
110
+ -h, --help Show this help
111
+
112
+ Examples:
113
+ ${BIN} new app
114
+ ${BIN} new secret --private
115
+ ${BIN} new app --init`);
116
+ return;
117
+ }
118
+ const name = trimmed(positionals[0]);
119
+ if (!name)
120
+ fail(`Repository name is required. Usage: ${BIN} new <name>`);
121
+ if (values.clone && values.init)
122
+ fail('Use either --clone or --init, not both.');
123
+ if (values.private && values.public)
124
+ fail('Use either --private or --public, not both.');
125
+ if (values.json && (values.clone || values.init)) {
126
+ fail('--json cannot be combined with --clone or --init.');
127
+ }
128
+ let isPrivate;
129
+ if (values.private)
130
+ isPrivate = true;
131
+ else if (values.public)
132
+ isPrivate = false;
133
+ else if (values.json || !process.stdin.isTTY)
134
+ isPrivate = false;
135
+ else
136
+ isPrivate = await askVisibility();
137
+ const config = loadConfig(text(values.url));
138
+ requireAuth(config);
139
+ const api = new ApiClient(config.url, config.token);
140
+ const repo = await api.newRepo(name, isPrivate);
141
+ if (values.json) {
142
+ printJson({ ...repo, url: config.url });
143
+ return;
144
+ }
145
+ if (repo.created)
146
+ ok(`Created ${repo.owner}/${repo.name}${repo.private ? ' (private)' : ''}`);
147
+ else
148
+ info(`Already exists: ${repo.owner}/${repo.name}`);
149
+ info(`Clone URL: ${repo.cloneUrl}`);
150
+ const dir = trimmed(values.dir) ?? repo.name;
151
+ if (values.clone) {
152
+ await cloneRepo(repo, dir, config);
153
+ ok(`Cloned into ${dir}`);
154
+ info(`cd ${dir}`);
155
+ return;
156
+ }
157
+ if (values.init) {
158
+ await initRepo(repo, dir, config);
159
+ ok(`Created ${dir}, made the first commit and pushed it`);
160
+ info(`cd ${dir}`);
161
+ return;
162
+ }
163
+ info(`Next: ${cyan(`${BIN} clone ${repo.owner}/${repo.name}`)}`);
164
+ }
165
+ export async function deleteCommand(argv) {
166
+ const { values, positionals } = parseArgs({
167
+ args: argv,
168
+ allowPositionals: true,
169
+ options: {
170
+ url: { type: 'string' },
171
+ yes: { type: 'boolean', short: 'y' },
172
+ json: { type: 'boolean' },
173
+ help: { type: 'boolean', short: 'h' },
174
+ },
175
+ });
176
+ if (values.help) {
177
+ console.log(`Usage: ${BIN} delete <name|owner/name> [options]
178
+
179
+ Options:
180
+ -y, --yes Do not ask for confirmation
181
+ --json Print the API response as JSON
182
+ --url <url> Server URL
183
+ -h, --help Show this help`);
184
+ return;
185
+ }
186
+ const target = trimmed(positionals[0]);
187
+ if (!target)
188
+ fail(`Repository name is required. Usage: ${BIN} delete <name|owner/name>`);
189
+ const parsed = parseTarget(target);
190
+ const config = loadConfig(text(values.url));
191
+ requireAuth(config);
192
+ const owner = parsed.owner ?? config.username;
193
+ if (!owner)
194
+ fail('No owner given and no username stored. Use <owner/name> or log in first.');
195
+ if (!values.yes) {
196
+ const approved = await confirm(`Delete ${owner}/${parsed.name}? This cannot be undone.`, false);
197
+ if (!approved) {
198
+ info('Cancelled.');
199
+ return;
200
+ }
201
+ }
202
+ const api = new ApiClient(config.url, config.token);
203
+ const result = await api.deleteRepo(owner, parsed.name);
204
+ if (values.json) {
205
+ printJson(result);
206
+ return;
207
+ }
208
+ ok(`Deleted ${result.owner}/${result.name}`);
209
+ }
210
+ export async function cloneCommand(argv) {
211
+ const { values, positionals } = parseArgs({
212
+ args: argv,
213
+ allowPositionals: true,
214
+ options: {
215
+ url: { type: 'string' },
216
+ dir: { type: 'string' },
217
+ json: { type: 'boolean' },
218
+ help: { type: 'boolean', short: 'h' },
219
+ },
220
+ });
221
+ if (values.help) {
222
+ console.log(`Usage: ${BIN} clone <name|owner/name> [directory] [options]
223
+
224
+ Clone a repository using the URL returned by the server.
225
+
226
+ Options:
227
+ --dir <path> Target directory (default: repository name)
228
+ --json Print repository details as JSON after cloning
229
+ --url <url> Server URL
230
+ -h, --help Show this help
231
+
232
+ Examples:
233
+ ${BIN} clone app
234
+ ${BIN} clone alice/app ./checkout`);
235
+ return;
236
+ }
237
+ const target = trimmed(positionals[0]);
238
+ if (!target)
239
+ fail(`Repository is required. Usage: ${BIN} clone <name|owner/name>`);
240
+ const parsed = parseTarget(target);
241
+ const config = loadConfig(text(values.url));
242
+ const owner = parsed.owner ?? config.username;
243
+ if (!owner)
244
+ fail('No owner given and no username stored. Use <owner/name> or log in first.');
245
+ const api = new ApiClient(config.url, config.token);
246
+ const repo = await api.repo(owner, parsed.name);
247
+ const dir = trimmed(values.dir) ?? trimmed(positionals[1]) ?? repo.name;
248
+ await cloneRepo(repo, dir, config);
249
+ if (values.json) {
250
+ printJson({ ...repo, directory: dir });
251
+ return;
252
+ }
253
+ ok(`Cloned ${repo.owner}/${repo.name} into ${dir}`);
254
+ info(`cd ${dir}`);
255
+ }
256
+ //# sourceMappingURL=repos.js.map