@hypercodingdev/zit 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,151 @@
1
+ # zit
2
+
3
+ A folder-based Git identity manager. Associate workspace folders with Git accounts — SSH keys, author name, and email are applied automatically to every repo inside.
4
+
5
+ **No manual switching.** No ssh-agent. No shell hooks. Uses git's native `includeIf` to make it work everywhere — terminal, VS Code, JetBrains, any git client.
6
+
7
+ ## How It Works
8
+
9
+ 1. Register your git accounts with `zit add`
10
+ 2. Create workspace folders linked to accounts with `zit workspace init`
11
+ 3. Clone repos inside workspace folders with `zit clone`
12
+ 4. After that, `git push`, `git pull`, `git commit` — everything uses the correct identity automatically
13
+
14
+ Under the hood, `zit` uses:
15
+ - **`includeIf "gitdir:"`** — git's native conditional config that auto-loads identity settings based on repo location
16
+ - **`core.sshCommand`** — tells git which SSH key to use, no ssh-agent needed
17
+
18
+ ## Installation
19
+
20
+ ```shell
21
+ npm install -g zit-cli
22
+ ```
23
+
24
+ Requires Node.js >= 18, `git`, and `ssh-keygen`.
25
+
26
+ ## Quick Start
27
+
28
+ ```shell
29
+ # Register an account
30
+ zit add
31
+ # Account name: work
32
+ # Git user name: John Doe
33
+ # Git email: john@company.com
34
+ # → Generates SSH key, prints public key to paste into GitHub/GitLab
35
+
36
+ # Create a workspace folder linked to that account
37
+ zit workspace init ~/work work
38
+ # → Creates ~/work/, configures git includeIf
39
+
40
+ # Clone a repo inside the workspace
41
+ cd ~/work
42
+ zit clone git@github.com:my-org/my-repo.git
43
+
44
+ # From now on, git just works — no zit needed
45
+ cd ~/work/my-repo
46
+ git commit -m "uses work identity automatically"
47
+ git push # uses work SSH key automatically
48
+ ```
49
+
50
+ ## Commands
51
+
52
+ ### `zit add`
53
+
54
+ Register a new git account interactively. Generates an SSH keypair.
55
+
56
+ ```shell
57
+ zit add # default: ed25519
58
+ zit add -t rsa # specify key type
59
+ ```
60
+
61
+ Supported key types: `ed25519` (default), `rsa`, `ecdsa`, `ecdsa-sk`, `ed25519-sk`, `dsa`
62
+
63
+ ### `zit remove <account>`
64
+
65
+ Remove an account and its SSH keys.
66
+
67
+ ```shell
68
+ zit remove work
69
+ zit remove work --force # also removes linked workspaces
70
+ ```
71
+
72
+ ### `zit list`
73
+
74
+ List all registered accounts with their emails.
75
+
76
+ ```shell
77
+ $ zit list
78
+ Registered accounts:
79
+
80
+ personal john@personal.com
81
+ work john@company.com
82
+ ```
83
+
84
+ ### `zit workspace init <path> <account>`
85
+
86
+ Create a workspace folder linked to an account.
87
+
88
+ ```shell
89
+ zit workspace init ~/work work
90
+ zit workspace init ~/personal personal
91
+ ```
92
+
93
+ ### `zit workspace list`
94
+
95
+ List all workspaces.
96
+
97
+ ```shell
98
+ $ zit workspace list
99
+ Workspaces:
100
+
101
+ work ~/work work
102
+ personal ~/personal personal
103
+ ```
104
+
105
+ ### `zit workspace remove <name>`
106
+
107
+ Unlink a workspace. Keeps the folder and repos, but removes the identity link.
108
+
109
+ ```shell
110
+ zit workspace remove work
111
+ ```
112
+
113
+ ### `zit clone <url>`
114
+
115
+ Clone a repo using the current workspace's SSH key. Must be run inside a workspace folder.
116
+
117
+ ```shell
118
+ cd ~/work
119
+ zit clone git@github.com:org/repo.git
120
+ zit clone git@github.com:org/repo.git --depth 1 # extra git clone flags work
121
+ ```
122
+
123
+ ### `zit status`
124
+
125
+ Show the current directory's workspace and account info.
126
+
127
+ ```shell
128
+ $ cd ~/work/my-repo && zit status
129
+ Workspace: work
130
+ Path: ~/work
131
+ Account: work
132
+ Name: John Doe
133
+ Email: john@company.com
134
+ SSH Key: ~/.ssh/id_ed25519_work
135
+ ```
136
+
137
+ ## What zit Does to Your System
138
+
139
+ - **`~/.config/zit/config`** — stores registered accounts and workspace mappings
140
+ - **`~/.config/zit/workspaces/*.gitconfig`** — per-workspace git config files
141
+ - **`~/.gitconfig`** — adds `includeIf` entries (one per workspace)
142
+ - **`~/.ssh/id_<type>_<name>`** — SSH keypairs for each account
143
+
144
+ ## Platform Support
145
+
146
+ - Linux
147
+ - macOS
148
+
149
+ ## License
150
+
151
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const commander_1 = require("commander");
5
+ const add_js_1 = require("./commands/add.js");
6
+ const remove_js_1 = require("./commands/remove.js");
7
+ const list_js_1 = require("./commands/list.js");
8
+ const workspace_js_1 = require("./commands/workspace.js");
9
+ const clone_js_1 = require("./commands/clone.js");
10
+ const status_js_1 = require("./commands/status.js");
11
+ const program = new commander_1.Command();
12
+ program
13
+ .name("zit")
14
+ .description("Folder-based Git identity manager. Associate workspace folders with Git accounts.")
15
+ .version("1.0.0");
16
+ (0, add_js_1.registerAddCommand)(program);
17
+ (0, remove_js_1.registerRemoveCommand)(program);
18
+ (0, list_js_1.registerListCommand)(program);
19
+ (0, workspace_js_1.registerWorkspaceCommand)(program);
20
+ (0, clone_js_1.registerCloneCommand)(program);
21
+ (0, status_js_1.registerStatusCommand)(program);
22
+ program.parse();
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerAddCommand(program: Command): void;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerAddCommand = registerAddCommand;
4
+ const config_js_1 = require("../config.js");
5
+ const ssh_js_1 = require("../ssh.js");
6
+ const utils_js_1 = require("../utils.js");
7
+ function registerAddCommand(program) {
8
+ program
9
+ .command("add")
10
+ .description("Register a new git account with SSH key generation")
11
+ .option("-t, --type <type>", "SSH key type (ed25519, rsa, ecdsa, ...)", "ed25519")
12
+ .action(async (opts) => {
13
+ if (!(0, ssh_js_1.isValidKeyType)(opts.type)) {
14
+ console.log((0, utils_js_1.red)(`Invalid key type: ${opts.type}`));
15
+ console.log("Valid types: ed25519, rsa, ecdsa, ecdsa-sk, ed25519-sk, dsa");
16
+ process.exit(1);
17
+ }
18
+ const keyType = opts.type;
19
+ const accountName = await (0, utils_js_1.prompt)("Account name: ");
20
+ if (!accountName) {
21
+ console.log((0, utils_js_1.red)("Account name is required."));
22
+ process.exit(1);
23
+ }
24
+ const config = (0, config_js_1.readConfig)();
25
+ if (config.accounts[accountName]) {
26
+ const overwrite = await (0, utils_js_1.promptYesNo)(`Account "${accountName}" already exists. Overwrite?`);
27
+ if (!overwrite) {
28
+ console.log((0, utils_js_1.yellow)("Cancelled."));
29
+ return;
30
+ }
31
+ }
32
+ const userName = await (0, utils_js_1.prompt)("Git user name: ");
33
+ const userEmail = await (0, utils_js_1.prompt)("Git email: ");
34
+ if (!userName || !userEmail) {
35
+ console.log((0, utils_js_1.red)("Name and email are required."));
36
+ process.exit(1);
37
+ }
38
+ const { privateKey, publicKey: publicKeyPath } = (0, ssh_js_1.getSshKeyPath)(accountName, keyType);
39
+ console.log(`\nGenerating ${keyType} SSH key...`);
40
+ const publicKey = (0, ssh_js_1.generateSshKey)(userEmail, privateKey, keyType);
41
+ config.accounts[accountName] = {
42
+ name: userName,
43
+ email: userEmail,
44
+ private_key: privateKey,
45
+ public_key: publicKeyPath,
46
+ };
47
+ (0, config_js_1.writeConfig)(config);
48
+ console.log((0, utils_js_1.green)("\nYour SSH public key:"));
49
+ console.log(publicKey);
50
+ console.log((0, utils_js_1.green)("\nPaste this key into your GitHub/GitLab SSH settings."));
51
+ console.log((0, utils_js_1.green)(`Account "${accountName}" registered successfully.`));
52
+ });
53
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerCloneCommand(program: Command): void;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerCloneCommand = registerCloneCommand;
4
+ const node_child_process_1 = require("node:child_process");
5
+ const config_js_1 = require("../config.js");
6
+ const utils_js_1 = require("../utils.js");
7
+ function registerCloneCommand(program) {
8
+ program
9
+ .command("clone <url>")
10
+ .description("Clone a repo using the current workspace's SSH key")
11
+ .allowUnknownOption(true)
12
+ .allowExcessArguments(true)
13
+ .action((url, _opts, cmd) => {
14
+ const cwd = process.cwd();
15
+ const match = (0, config_js_1.findWorkspaceByPath)(cwd);
16
+ if (!match) {
17
+ console.log((0, utils_js_1.red)("Not inside a zit workspace."));
18
+ const config = (0, config_js_1.readConfig)();
19
+ const workspaces = Object.entries(config.workspaces);
20
+ if (workspaces.length > 0) {
21
+ console.log("\nAvailable workspaces:");
22
+ for (const [name, ws] of workspaces) {
23
+ console.log(` ${name} ${(0, utils_js_1.collapseHome)(ws.path)}`);
24
+ }
25
+ console.log((0, utils_js_1.yellow)("\ncd into a workspace folder first, then run `zit clone <url>`."));
26
+ }
27
+ else {
28
+ console.log((0, utils_js_1.yellow)("Create a workspace first: `zit workspace init <path> <account>`"));
29
+ }
30
+ process.exit(1);
31
+ }
32
+ const { name, workspace } = match;
33
+ const config = (0, config_js_1.readConfig)();
34
+ const account = config.accounts[workspace.account];
35
+ if (!account) {
36
+ console.log((0, utils_js_1.red)(`Account "${workspace.account}" referenced by workspace "${name}" not found.`));
37
+ process.exit(1);
38
+ }
39
+ // Collect any extra args passed after the url
40
+ const extraArgs = cmd.args.slice(1).join(" ");
41
+ const sshCommand = `ssh -i ${account.private_key} -o IdentitiesOnly=yes`;
42
+ console.log(`Cloning with account "${workspace.account}" (${account.email})...`);
43
+ try {
44
+ const fullCmd = `GIT_SSH_COMMAND='${sshCommand}' git clone ${url}${extraArgs ? " " + extraArgs : ""}`;
45
+ (0, node_child_process_1.execSync)(fullCmd, { stdio: "inherit", cwd });
46
+ console.log((0, utils_js_1.green)("\nClone complete. Push/pull will use this identity automatically."));
47
+ }
48
+ catch {
49
+ console.log((0, utils_js_1.red)("\nClone failed."));
50
+ process.exit(1);
51
+ }
52
+ });
53
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerListCommand(program: Command): void;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerListCommand = registerListCommand;
4
+ const config_js_1 = require("../config.js");
5
+ const utils_js_1 = require("../utils.js");
6
+ function registerListCommand(program) {
7
+ program
8
+ .command("list")
9
+ .description("List all registered accounts")
10
+ .action(() => {
11
+ const config = (0, config_js_1.readConfig)();
12
+ const accounts = Object.entries(config.accounts);
13
+ if (accounts.length === 0) {
14
+ console.log((0, utils_js_1.yellow)("No accounts registered. Use `zit add` to register one."));
15
+ return;
16
+ }
17
+ console.log((0, utils_js_1.green)("Registered accounts:\n"));
18
+ const maxNameLen = Math.max(...accounts.map(([key]) => key.length));
19
+ for (const [key, acc] of accounts) {
20
+ console.log(` ${key.padEnd(maxNameLen + 2)} ${acc.email}`);
21
+ }
22
+ });
23
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerRemoveCommand(program: Command): void;
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerRemoveCommand = registerRemoveCommand;
4
+ const config_js_1 = require("../config.js");
5
+ const ssh_js_1 = require("../ssh.js");
6
+ const gitconfig_js_1 = require("../gitconfig.js");
7
+ const config_js_2 = require("../config.js");
8
+ const utils_js_1 = require("../utils.js");
9
+ function registerRemoveCommand(program) {
10
+ program
11
+ .command("remove <account>")
12
+ .description("Remove a registered git account and its SSH keys")
13
+ .option("--force", "Remove even if workspaces reference this account")
14
+ .action((accountName, opts) => {
15
+ const config = (0, config_js_1.readConfig)();
16
+ const account = config.accounts[accountName];
17
+ if (!account) {
18
+ console.log((0, utils_js_1.red)(`Account "${accountName}" not found.`));
19
+ process.exit(1);
20
+ }
21
+ // Check if any workspaces reference this account
22
+ const linkedWorkspaces = Object.entries(config.workspaces).filter(([, ws]) => ws.account === accountName);
23
+ if (linkedWorkspaces.length > 0 && !opts.force) {
24
+ console.log((0, utils_js_1.red)(`Account "${accountName}" is used by these workspaces:`));
25
+ for (const [name, ws] of linkedWorkspaces) {
26
+ console.log(` - ${name} (${ws.path})`);
27
+ }
28
+ console.log((0, utils_js_1.yellow)("\nRemove the workspaces first, or use --force to remove everything."));
29
+ process.exit(1);
30
+ }
31
+ // If --force, also remove linked workspaces
32
+ if (linkedWorkspaces.length > 0 && opts.force) {
33
+ for (const [name, ws] of linkedWorkspaces) {
34
+ (0, gitconfig_js_1.removeIncludeIf)(ws.path);
35
+ (0, gitconfig_js_1.removeWorkspaceGitconfig)((0, config_js_2.getWorkspaceGitconfigPath)(name));
36
+ delete config.workspaces[name];
37
+ console.log((0, utils_js_1.yellow)(`Removed workspace "${name}".`));
38
+ }
39
+ }
40
+ // Remove SSH keys
41
+ (0, ssh_js_1.removeSshKey)(account.private_key);
42
+ // Remove from config
43
+ delete config.accounts[accountName];
44
+ (0, config_js_1.writeConfig)(config);
45
+ console.log((0, utils_js_1.green)(`Account "${accountName}" removed.`));
46
+ });
47
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerStatusCommand(program: Command): void;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerStatusCommand = registerStatusCommand;
4
+ const config_js_1 = require("../config.js");
5
+ const utils_js_1 = require("../utils.js");
6
+ function registerStatusCommand(program) {
7
+ program
8
+ .command("status")
9
+ .description("Show the current directory's workspace and account info")
10
+ .action(() => {
11
+ const cwd = process.cwd();
12
+ const match = (0, config_js_1.findWorkspaceByPath)(cwd);
13
+ if (!match) {
14
+ console.log((0, utils_js_1.yellow)("Not inside a zit workspace."));
15
+ return;
16
+ }
17
+ const { name, workspace } = match;
18
+ const config = (0, config_js_1.readConfig)();
19
+ const account = config.accounts[workspace.account];
20
+ console.log((0, utils_js_1.green)("Workspace: ") + name);
21
+ console.log((0, utils_js_1.green)("Path: ") + (0, utils_js_1.collapseHome)(workspace.path));
22
+ console.log((0, utils_js_1.green)("Account: ") + workspace.account);
23
+ if (account) {
24
+ console.log((0, utils_js_1.green)("Name: ") + account.name);
25
+ console.log((0, utils_js_1.green)("Email: ") + account.email);
26
+ console.log((0, utils_js_1.green)("SSH Key: ") + (0, utils_js_1.collapseHome)(account.private_key));
27
+ }
28
+ else {
29
+ console.log((0, utils_js_1.yellow)(`Warning: Account "${workspace.account}" not found in config.`));
30
+ }
31
+ });
32
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerWorkspaceCommand(program: Command): void;
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.registerWorkspaceCommand = registerWorkspaceCommand;
37
+ const fs = __importStar(require("node:fs"));
38
+ const config_js_1 = require("../config.js");
39
+ const gitconfig_js_1 = require("../gitconfig.js");
40
+ const utils_js_1 = require("../utils.js");
41
+ function registerWorkspaceCommand(program) {
42
+ const ws = program
43
+ .command("workspace")
44
+ .description("Manage workspace folders linked to accounts");
45
+ ws.command("init <path> <account>")
46
+ .description("Create a workspace folder linked to an account")
47
+ .action((wsPath, accountName) => {
48
+ const config = (0, config_js_1.readConfig)();
49
+ const account = config.accounts[accountName];
50
+ if (!account) {
51
+ console.log((0, utils_js_1.red)(`Account "${accountName}" not found.`));
52
+ console.log("Use `zit list` to see registered accounts.");
53
+ process.exit(1);
54
+ }
55
+ const resolvedPath = (0, utils_js_1.resolvePath)(wsPath);
56
+ // Check if a workspace already exists for this path
57
+ for (const [name, existing] of Object.entries(config.workspaces)) {
58
+ if ((0, utils_js_1.resolvePath)(existing.path) === resolvedPath) {
59
+ console.log((0, utils_js_1.yellow)(`Workspace "${name}" already exists for this path. Updating account to "${accountName}".`));
60
+ // Update existing workspace
61
+ existing.account = accountName;
62
+ const gitconfigPath = (0, config_js_1.getWorkspaceGitconfigPath)(name);
63
+ (0, gitconfig_js_1.writeWorkspaceGitconfig)(gitconfigPath, account.name, account.email, account.private_key);
64
+ (0, config_js_1.writeConfig)(config);
65
+ console.log((0, utils_js_1.green)(`Workspace "${name}" updated.`));
66
+ return;
67
+ }
68
+ }
69
+ // Create the folder
70
+ fs.mkdirSync(resolvedPath, { recursive: true });
71
+ // Derive workspace name from the folder name
72
+ const wsName = resolvedPath.split("/").filter(Boolean).pop();
73
+ // Check name collision
74
+ let finalName = wsName;
75
+ if (config.workspaces[finalName]) {
76
+ let i = 2;
77
+ while (config.workspaces[`${wsName}-${i}`])
78
+ i++;
79
+ finalName = `${wsName}-${i}`;
80
+ }
81
+ // Generate workspace gitconfig
82
+ const gitconfigPath = (0, config_js_1.getWorkspaceGitconfigPath)(finalName);
83
+ (0, gitconfig_js_1.writeWorkspaceGitconfig)(gitconfigPath, account.name, account.email, account.private_key);
84
+ // Add includeIf to ~/.gitconfig
85
+ (0, gitconfig_js_1.addIncludeIf)(resolvedPath, gitconfigPath);
86
+ // Save workspace to config
87
+ config.workspaces[finalName] = {
88
+ path: resolvedPath,
89
+ account: accountName,
90
+ };
91
+ (0, config_js_1.writeConfig)(config);
92
+ console.log((0, utils_js_1.green)(`Workspace "${finalName}" created.`));
93
+ console.log(` Path: ${(0, utils_js_1.collapseHome)(resolvedPath)}`);
94
+ console.log(` Account: ${accountName} (${account.email})`);
95
+ console.log(`\nRepos cloned inside ${(0, utils_js_1.collapseHome)(resolvedPath)} will use this identity automatically.`);
96
+ });
97
+ ws.command("list")
98
+ .description("List all workspaces and their linked accounts")
99
+ .action(() => {
100
+ const config = (0, config_js_1.readConfig)();
101
+ const workspaces = Object.entries(config.workspaces);
102
+ if (workspaces.length === 0) {
103
+ console.log((0, utils_js_1.yellow)("No workspaces configured. Use `zit workspace init <path> <account>` to create one."));
104
+ return;
105
+ }
106
+ console.log((0, utils_js_1.green)("Workspaces:\n"));
107
+ const maxNameLen = Math.max(...workspaces.map(([name]) => name.length));
108
+ const maxPathLen = Math.max(...workspaces.map(([, ws]) => (0, utils_js_1.collapseHome)(ws.path).length));
109
+ for (const [name, ws] of workspaces) {
110
+ console.log(` ${name.padEnd(maxNameLen + 2)} ${(0, utils_js_1.collapseHome)(ws.path).padEnd(maxPathLen + 2)} ${ws.account}`);
111
+ }
112
+ });
113
+ ws.command("remove <name>")
114
+ .description("Unlink a workspace (keeps the folder and repos)")
115
+ .action((name) => {
116
+ const config = (0, config_js_1.readConfig)();
117
+ const workspace = config.workspaces[name];
118
+ if (!workspace) {
119
+ console.log((0, utils_js_1.red)(`Workspace "${name}" not found.`));
120
+ console.log("Use `zit workspace list` to see available workspaces.");
121
+ process.exit(1);
122
+ }
123
+ // Remove includeIf from ~/.gitconfig
124
+ (0, gitconfig_js_1.removeIncludeIf)(workspace.path);
125
+ // Remove workspace gitconfig file
126
+ (0, gitconfig_js_1.removeWorkspaceGitconfig)((0, config_js_1.getWorkspaceGitconfigPath)(name));
127
+ // Remove from config
128
+ delete config.workspaces[name];
129
+ (0, config_js_1.writeConfig)(config);
130
+ console.log((0, utils_js_1.green)(`Workspace "${name}" removed.`));
131
+ console.log((0, utils_js_1.yellow)("The folder and its repos still exist, but they no longer use the linked identity."));
132
+ });
133
+ }
@@ -0,0 +1,25 @@
1
+ export interface Account {
2
+ name: string;
3
+ email: string;
4
+ private_key: string;
5
+ public_key: string;
6
+ }
7
+ export interface Workspace {
8
+ path: string;
9
+ account: string;
10
+ }
11
+ export interface ZitConfig {
12
+ accounts: Record<string, Account>;
13
+ workspaces: Record<string, Workspace>;
14
+ }
15
+ export declare function getConfigPath(): string;
16
+ export declare function getWorkspacesDir(): string;
17
+ export declare function getWorkspaceGitconfigPath(name: string): string;
18
+ export declare function readConfig(): ZitConfig;
19
+ export declare function writeConfig(config: ZitConfig): void;
20
+ export declare function getAccount(name: string): Account | undefined;
21
+ export declare function getWorkspace(name: string): Workspace | undefined;
22
+ export declare function findWorkspaceByPath(dir: string): {
23
+ name: string;
24
+ workspace: Workspace;
25
+ } | null;
package/dist/config.js ADDED
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getConfigPath = getConfigPath;
37
+ exports.getWorkspacesDir = getWorkspacesDir;
38
+ exports.getWorkspaceGitconfigPath = getWorkspaceGitconfigPath;
39
+ exports.readConfig = readConfig;
40
+ exports.writeConfig = writeConfig;
41
+ exports.getAccount = getAccount;
42
+ exports.getWorkspace = getWorkspace;
43
+ exports.findWorkspaceByPath = findWorkspaceByPath;
44
+ const fs = __importStar(require("node:fs"));
45
+ const path = __importStar(require("node:path"));
46
+ const os = __importStar(require("node:os"));
47
+ const ini = __importStar(require("ini"));
48
+ function getConfigDir() {
49
+ const xdg = process.env.XDG_CONFIG_HOME;
50
+ const base = xdg || path.join(os.homedir(), ".config");
51
+ return path.join(base, "zit");
52
+ }
53
+ function getConfigPath() {
54
+ return path.join(getConfigDir(), "config");
55
+ }
56
+ function getWorkspacesDir() {
57
+ return path.join(getConfigDir(), "workspaces");
58
+ }
59
+ function getWorkspaceGitconfigPath(name) {
60
+ return path.join(getWorkspacesDir(), `${name}.gitconfig`);
61
+ }
62
+ function ensureConfigDir() {
63
+ const dir = getConfigDir();
64
+ fs.mkdirSync(dir, { recursive: true });
65
+ fs.mkdirSync(getWorkspacesDir(), { recursive: true });
66
+ }
67
+ function readConfig() {
68
+ const configPath = getConfigPath();
69
+ const config = { accounts: {}, workspaces: {} };
70
+ if (!fs.existsSync(configPath)) {
71
+ return config;
72
+ }
73
+ const raw = fs.readFileSync(configPath, "utf-8");
74
+ const parsed = ini.parse(raw);
75
+ for (const [key, value] of Object.entries(parsed)) {
76
+ if (typeof value === "object" &&
77
+ value !== null &&
78
+ "name" in value &&
79
+ "email" in value) {
80
+ if ("private_key" in value && "public_key" in value) {
81
+ config.accounts[key] = value;
82
+ }
83
+ else if ("path" in value && "account" in value) {
84
+ config.workspaces[key] = value;
85
+ }
86
+ }
87
+ }
88
+ return config;
89
+ }
90
+ function writeConfig(config) {
91
+ ensureConfigDir();
92
+ const configPath = getConfigPath();
93
+ const obj = {};
94
+ for (const [key, acc] of Object.entries(config.accounts)) {
95
+ obj[key] = {
96
+ name: acc.name,
97
+ email: acc.email,
98
+ private_key: acc.private_key,
99
+ public_key: acc.public_key,
100
+ };
101
+ }
102
+ for (const [key, ws] of Object.entries(config.workspaces)) {
103
+ obj[`workspace "${key}"`] = {
104
+ path: ws.path,
105
+ account: ws.account,
106
+ };
107
+ }
108
+ fs.writeFileSync(configPath, ini.stringify(obj), "utf-8");
109
+ }
110
+ function getAccount(name) {
111
+ const config = readConfig();
112
+ return config.accounts[name];
113
+ }
114
+ function getWorkspace(name) {
115
+ const config = readConfig();
116
+ return config.workspaces[name];
117
+ }
118
+ function findWorkspaceByPath(dir) {
119
+ const config = readConfig();
120
+ const resolved = path.resolve(dir);
121
+ let bestMatch = null;
122
+ let bestLen = 0;
123
+ for (const [name, ws] of Object.entries(config.workspaces)) {
124
+ const wsPath = path.resolve(ws.path);
125
+ if ((resolved === wsPath || resolved.startsWith(wsPath + "/")) &&
126
+ wsPath.length > bestLen) {
127
+ bestMatch = { name, workspace: ws };
128
+ bestLen = wsPath.length;
129
+ }
130
+ }
131
+ return bestMatch;
132
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Add an includeIf entry to ~/.gitconfig for a workspace.
3
+ * Format:
4
+ * [includeIf "gitdir:<path>/"]
5
+ * path = <gitconfig_path>
6
+ */
7
+ export declare function addIncludeIf(workspacePath: string, gitconfigPath: string): void;
8
+ /**
9
+ * Remove an includeIf entry from ~/.gitconfig for a workspace.
10
+ */
11
+ export declare function removeIncludeIf(workspacePath: string): void;
12
+ /**
13
+ * Write a per-workspace .gitconfig file with user identity and SSH command.
14
+ */
15
+ export declare function writeWorkspaceGitconfig(gitconfigPath: string, userName: string, userEmail: string, privateKeyPath: string): void;
16
+ /**
17
+ * Remove a per-workspace .gitconfig file.
18
+ */
19
+ export declare function removeWorkspaceGitconfig(gitconfigPath: string): void;
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.addIncludeIf = addIncludeIf;
37
+ exports.removeIncludeIf = removeIncludeIf;
38
+ exports.writeWorkspaceGitconfig = writeWorkspaceGitconfig;
39
+ exports.removeWorkspaceGitconfig = removeWorkspaceGitconfig;
40
+ const fs = __importStar(require("node:fs"));
41
+ const path = __importStar(require("node:path"));
42
+ const os = __importStar(require("node:os"));
43
+ const utils_js_1 = require("./utils.js");
44
+ function getGlobalGitconfigPath() {
45
+ return path.join(os.homedir(), ".gitconfig");
46
+ }
47
+ /**
48
+ * Add an includeIf entry to ~/.gitconfig for a workspace.
49
+ * Format:
50
+ * [includeIf "gitdir:<path>/"]
51
+ * path = <gitconfig_path>
52
+ */
53
+ function addIncludeIf(workspacePath, gitconfigPath) {
54
+ const globalPath = getGlobalGitconfigPath();
55
+ const dirPattern = (0, utils_js_1.ensureTrailingSlash)(workspacePath);
56
+ let content = "";
57
+ if (fs.existsSync(globalPath)) {
58
+ content = fs.readFileSync(globalPath, "utf-8");
59
+ }
60
+ // Check if this includeIf already exists
61
+ const marker = `[includeIf "gitdir:${dirPattern}"]`;
62
+ if (content.includes(marker)) {
63
+ // Update the path in case it changed
64
+ const lines = content.split("\n");
65
+ const idx = lines.findIndex((l) => l.trim() === marker);
66
+ if (idx !== -1 && idx + 1 < lines.length) {
67
+ lines[idx + 1] = `\tpath = ${gitconfigPath}`;
68
+ }
69
+ fs.writeFileSync(globalPath, lines.join("\n"), "utf-8");
70
+ return;
71
+ }
72
+ // Append new entry
73
+ const entry = `\n${marker}\n\tpath = ${gitconfigPath}\n`;
74
+ fs.writeFileSync(globalPath, content + entry, "utf-8");
75
+ }
76
+ /**
77
+ * Remove an includeIf entry from ~/.gitconfig for a workspace.
78
+ */
79
+ function removeIncludeIf(workspacePath) {
80
+ const globalPath = getGlobalGitconfigPath();
81
+ if (!fs.existsSync(globalPath))
82
+ return;
83
+ const dirPattern = (0, utils_js_1.ensureTrailingSlash)(workspacePath);
84
+ const marker = `[includeIf "gitdir:${dirPattern}"]`;
85
+ const content = fs.readFileSync(globalPath, "utf-8");
86
+ if (!content.includes(marker))
87
+ return;
88
+ const lines = content.split("\n");
89
+ const result = [];
90
+ let i = 0;
91
+ while (i < lines.length) {
92
+ if (lines[i].trim() === marker) {
93
+ // Skip this line and any indented lines that follow (the section body)
94
+ i++;
95
+ while (i < lines.length && /^\s+/.test(lines[i]) && lines[i].trim()) {
96
+ i++;
97
+ }
98
+ // Skip trailing blank line
99
+ if (i < lines.length && lines[i].trim() === "") {
100
+ i++;
101
+ }
102
+ continue;
103
+ }
104
+ result.push(lines[i]);
105
+ i++;
106
+ }
107
+ fs.writeFileSync(globalPath, result.join("\n"), "utf-8");
108
+ }
109
+ /**
110
+ * Write a per-workspace .gitconfig file with user identity and SSH command.
111
+ */
112
+ function writeWorkspaceGitconfig(gitconfigPath, userName, userEmail, privateKeyPath) {
113
+ const dir = path.dirname(gitconfigPath);
114
+ fs.mkdirSync(dir, { recursive: true });
115
+ const content = `[user]
116
+ \tname = ${userName}
117
+ \temail = ${userEmail}
118
+ [core]
119
+ \tsshCommand = ssh -i ${privateKeyPath} -o IdentitiesOnly=yes
120
+ `;
121
+ fs.writeFileSync(gitconfigPath, content, "utf-8");
122
+ }
123
+ /**
124
+ * Remove a per-workspace .gitconfig file.
125
+ */
126
+ function removeWorkspaceGitconfig(gitconfigPath) {
127
+ if (fs.existsSync(gitconfigPath)) {
128
+ fs.unlinkSync(gitconfigPath);
129
+ }
130
+ }
package/dist/ssh.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ export type SshKeyType = "ed25519" | "rsa" | "ecdsa" | "ecdsa-sk" | "ed25519-sk" | "dsa";
2
+ export declare function isValidKeyType(t: string): t is SshKeyType;
3
+ export declare function getSshKeyPath(accountName: string, keyType: SshKeyType): {
4
+ privateKey: string;
5
+ publicKey: string;
6
+ };
7
+ export declare function generateSshKey(email: string, privateKeyPath: string, keyType: SshKeyType): string;
8
+ export declare function removeSshKey(privateKeyPath: string): void;
package/dist/ssh.js ADDED
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.isValidKeyType = isValidKeyType;
37
+ exports.getSshKeyPath = getSshKeyPath;
38
+ exports.generateSshKey = generateSshKey;
39
+ exports.removeSshKey = removeSshKey;
40
+ const path = __importStar(require("node:path"));
41
+ const os = __importStar(require("node:os"));
42
+ const fs = __importStar(require("node:fs"));
43
+ const node_child_process_1 = require("node:child_process");
44
+ const VALID_KEY_TYPES = [
45
+ "ed25519",
46
+ "rsa",
47
+ "ecdsa",
48
+ "ecdsa-sk",
49
+ "ed25519-sk",
50
+ "dsa",
51
+ ];
52
+ function isValidKeyType(t) {
53
+ return VALID_KEY_TYPES.includes(t);
54
+ }
55
+ function getSshKeyPath(accountName, keyType) {
56
+ const sshDir = path.join(os.homedir(), ".ssh");
57
+ const base = path.join(sshDir, `id_${keyType}_${accountName}`);
58
+ return {
59
+ privateKey: base,
60
+ publicKey: base + ".pub",
61
+ };
62
+ }
63
+ function generateSshKey(email, privateKeyPath, keyType) {
64
+ const sshDir = path.dirname(privateKeyPath);
65
+ fs.mkdirSync(sshDir, { recursive: true });
66
+ (0, node_child_process_1.execSync)(`ssh-keygen -t ${keyType} -C "${email}" -f "${privateKeyPath}" -N ""`, { stdio: "inherit" });
67
+ const publicKey = fs.readFileSync(privateKeyPath + ".pub", "utf-8").trim();
68
+ return publicKey;
69
+ }
70
+ function removeSshKey(privateKeyPath) {
71
+ if (fs.existsSync(privateKeyPath)) {
72
+ fs.unlinkSync(privateKeyPath);
73
+ }
74
+ const pubPath = privateKeyPath + ".pub";
75
+ if (fs.existsSync(pubPath)) {
76
+ fs.unlinkSync(pubPath);
77
+ }
78
+ }
@@ -0,0 +1,12 @@
1
+ export declare function expandHome(p: string): string;
2
+ export declare function resolvePath(p: string): string;
3
+ /** Collapse home directory to ~ for display */
4
+ export declare function collapseHome(p: string): string;
5
+ export declare function ensureTrailingSlash(p: string): string;
6
+ export declare function red(s: string): string;
7
+ export declare function green(s: string): string;
8
+ export declare function yellow(s: string): string;
9
+ export declare function blue(s: string): string;
10
+ export declare function bold(s: string): string;
11
+ export declare function prompt(question: string): Promise<string>;
12
+ export declare function promptYesNo(question: string): Promise<boolean>;
package/dist/utils.js ADDED
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.expandHome = expandHome;
37
+ exports.resolvePath = resolvePath;
38
+ exports.collapseHome = collapseHome;
39
+ exports.ensureTrailingSlash = ensureTrailingSlash;
40
+ exports.red = red;
41
+ exports.green = green;
42
+ exports.yellow = yellow;
43
+ exports.blue = blue;
44
+ exports.bold = bold;
45
+ exports.prompt = prompt;
46
+ exports.promptYesNo = promptYesNo;
47
+ const path = __importStar(require("node:path"));
48
+ const os = __importStar(require("node:os"));
49
+ const readline = __importStar(require("node:readline"));
50
+ function expandHome(p) {
51
+ if (p.startsWith("~/") || p === "~") {
52
+ return path.join(os.homedir(), p.slice(1));
53
+ }
54
+ return p;
55
+ }
56
+ function resolvePath(p) {
57
+ return path.resolve(expandHome(p));
58
+ }
59
+ /** Collapse home directory to ~ for display */
60
+ function collapseHome(p) {
61
+ const home = os.homedir();
62
+ if (p.startsWith(home + "/") || p === home) {
63
+ return "~" + p.slice(home.length);
64
+ }
65
+ return p;
66
+ }
67
+ function ensureTrailingSlash(p) {
68
+ return p.endsWith("/") ? p : p + "/";
69
+ }
70
+ const colors = {
71
+ red: "\x1b[31m",
72
+ green: "\x1b[32m",
73
+ yellow: "\x1b[33m",
74
+ blue: "\x1b[34m",
75
+ bold: "\x1b[1m",
76
+ reset: "\x1b[0m",
77
+ };
78
+ function red(s) {
79
+ return `${colors.red}${s}${colors.reset}`;
80
+ }
81
+ function green(s) {
82
+ return `${colors.green}${s}${colors.reset}`;
83
+ }
84
+ function yellow(s) {
85
+ return `${colors.yellow}${s}${colors.reset}`;
86
+ }
87
+ function blue(s) {
88
+ return `${colors.blue}${s}${colors.reset}`;
89
+ }
90
+ function bold(s) {
91
+ return `${colors.bold}${s}${colors.reset}`;
92
+ }
93
+ function prompt(question) {
94
+ const rl = readline.createInterface({
95
+ input: process.stdin,
96
+ output: process.stdout,
97
+ });
98
+ return new Promise((resolve) => {
99
+ rl.question(question, (answer) => {
100
+ rl.close();
101
+ resolve(answer.trim());
102
+ });
103
+ });
104
+ }
105
+ function promptYesNo(question) {
106
+ return prompt(`${yellow(question)} [y/n] `).then((answer) => {
107
+ const lower = answer.toLowerCase();
108
+ if (lower === "y" || lower === "yes")
109
+ return true;
110
+ if (lower === "n" || lower === "no")
111
+ return false;
112
+ console.log(red("Please answer y or n."));
113
+ return promptYesNo(question);
114
+ });
115
+ }
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@hypercodingdev/zit",
3
+ "publishConfig": {
4
+ "access": "public"
5
+ },
6
+ "version": "1.0.0",
7
+ "description": "Folder-based Git identity manager. Associate workspace folders with Git accounts — SSH keys, author name, and email are applied automatically.",
8
+ "main": "dist/cli.js",
9
+ "bin": {
10
+ "zit": "dist/cli.js"
11
+ },
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "dev": "tsc --watch",
15
+ "start": "node dist/cli.js",
16
+ "prepare": "tsc"
17
+ },
18
+ "keywords": [
19
+ "git",
20
+ "ssh",
21
+ "identity",
22
+ "account",
23
+ "workspace",
24
+ "multi-account"
25
+ ],
26
+ "license": "MIT",
27
+ "engines": {
28
+ "node": ">=18"
29
+ },
30
+ "files": [
31
+ "dist"
32
+ ],
33
+ "dependencies": {
34
+ "commander": "^12.0.0",
35
+ "ini": "^4.1.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/ini": "^4.1.0",
39
+ "@types/node": "^20.0.0",
40
+ "typescript": "^5.4.0"
41
+ }
42
+ }