@gwatch/gwatch-cli 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/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # G-Watch CLI
2
+
3
+ G-Watch CLI is a tool that wraps Docker and Tmux to manage persistent development environments.
4
+
5
+ ## Features
6
+
7
+ - **Workspace Management**: Clone remote repos or map local directories.
8
+ - **Environment Automation**: Automatically installs Node.js, Tmux, and `@google/gemini-cli` inside the container.
9
+ - **Session Management**: Wraps Tmux sessions for persistent development.
10
+ - **Easy Access**: Simple commands to create, enter, list, and stop environments.
11
+
12
+ ## Installation
13
+
14
+ Install globally via npm:
15
+
16
+ ```bash
17
+ npm install -g @gwatch/gwatch-cli
18
+ ```
19
+
20
+ Alternatively, to install from source:
21
+
22
+ ```bash
23
+ git clone https://github.com/G-Watch/Watchtower.git
24
+ cd Watchtower
25
+ npm install -g .
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ ### 1. Create a container from a remote git repo
31
+ ```bash
32
+ gwatch-cli create --repo https://github.com/user/project.git --dockerfile ./Dockerfile
33
+ ```
34
+
35
+ ### 2. Create a container from current local directory
36
+ ```bash
37
+ gwatch-cli create --dir . --dockerfile ./Dockerfile
38
+ ```
39
+
40
+ ### 3. Enter a container/session
41
+ ```bash
42
+ # Enter container 0, default session
43
+ gwatch-cli enter 0
44
+
45
+ # Enter container 0, specific session (creates if not exists)
46
+ gwatch-cli enter 0/debug_sass
47
+ ```
48
+
49
+ ### 4. List status
50
+ ```bash
51
+ gwatch-cli ls
52
+ ```
53
+
54
+ ### 5. Stop commands
55
+ ```bash
56
+ # Stops the Docker Container
57
+ gwatch-cli stop 0
58
+
59
+ # Kills only the specific tmux session inside the container
60
+ gwatch-cli stop 0/debug_sass
61
+ ```
62
+
63
+ ## Internal Database
64
+ Metadata is stored in `~/.watchtower/db.json`.
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createAction = createAction;
7
+ const path_1 = __importDefault(require("path"));
8
+ const os_1 = __importDefault(require("os"));
9
+ const fs_extra_1 = __importDefault(require("fs-extra"));
10
+ const docker_1 = require("../docker");
11
+ const db_1 = require("../db");
12
+ const ui_1 = require("../utils/ui");
13
+ async function createAction(options) {
14
+ const { repo, dir, dockerfile, name: suffix } = options;
15
+ if (!repo && !dir) {
16
+ console.error('Error: You must provide either --repo or --dir');
17
+ process.exit(1);
18
+ }
19
+ let workspacePath;
20
+ let baseName;
21
+ if (repo) {
22
+ baseName = repo.split('/').pop()?.replace('.git', '') || 'unknown';
23
+ workspacePath = path_1.default.join(os_1.default.tmpdir(), 'watchtower', baseName);
24
+ await fs_extra_1.default.ensureDir(workspacePath);
25
+ await (0, docker_1.cloneRepo)(repo, workspacePath);
26
+ }
27
+ else {
28
+ workspacePath = path_1.default.resolve(dir);
29
+ baseName = path_1.default.basename(workspacePath);
30
+ }
31
+ const userName = os_1.default.userInfo().username;
32
+ const db = await (0, db_1.readDb)();
33
+ let index = 1;
34
+ const generateContainerName = (idx) => {
35
+ const parts = [baseName, userName];
36
+ if (suffix)
37
+ parts.push(suffix);
38
+ parts.push(idx.toString());
39
+ return parts.join('-');
40
+ };
41
+ let containerName = generateContainerName(index);
42
+ while (db.containers.some(c => c.name === containerName)) {
43
+ index++;
44
+ containerName = generateContainerName(index);
45
+ }
46
+ const imageName = await (0, docker_1.buildImage)(dockerfile, baseName, workspacePath);
47
+ const container = await (0, docker_1.createContainer)(imageName, containerName, workspacePath);
48
+ console.log('Setting up environment (Node.js, Tmux, Gemini CLI)...');
49
+ await (0, docker_1.ensureEnvironment)(container.id);
50
+ console.log('Setting up default tmux session...');
51
+ await (0, docker_1.setupDefaultTmux)(container.id);
52
+ await (0, db_1.addContainer)({
53
+ id: container.id,
54
+ name: containerName,
55
+ creator: userName,
56
+ repoUrl: repo,
57
+ localDir: dir ? path_1.default.resolve(dir) : undefined,
58
+ sessions: [{ name: 'default', lastAccess: new Date().toISOString() }],
59
+ });
60
+ (0, ui_1.logSuccess)(`Container ${containerName} created successfully! (Index: ${db.containers.length})`);
61
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.enterAction = enterAction;
4
+ const db_1 = require("../db");
5
+ const execa_1 = require("execa");
6
+ const ui_1 = require("../utils/ui");
7
+ async function enterAction(target) {
8
+ const [indexStr, sessionName] = target.split('/');
9
+ const index = parseInt(indexStr, 10);
10
+ const db = await (0, db_1.readDb)();
11
+ const container = db.containers[index];
12
+ if (!container) {
13
+ (0, ui_1.logError)(`Container at index ${index} not found.`);
14
+ process.exit(1);
15
+ }
16
+ const finalSessionName = sessionName || 'default';
17
+ // Check if session exists, if not create it
18
+ try {
19
+ await (0, execa_1.execa)('docker', ['exec', container.id, 'tmux', 'has-session', '-t', finalSessionName]);
20
+ }
21
+ catch (e) {
22
+ (0, ui_1.logInfo)(`Session ${finalSessionName} not found. Creating it...`);
23
+ await (0, execa_1.execa)('docker', ['exec', container.id, 'tmux', 'new-session', '-d', '-s', finalSessionName]);
24
+ }
25
+ await (0, db_1.updateContainerSession)(container.name, finalSessionName);
26
+ (0, ui_1.logInfo)(`Entering session ${finalSessionName} in container ${container.name}...`);
27
+ try {
28
+ await (0, execa_1.execa)('docker', ['exec', '-it', container.id, 'tmux', 'attach', '-t', finalSessionName], {
29
+ stdio: 'inherit',
30
+ });
31
+ }
32
+ catch (e) {
33
+ (0, ui_1.logError)(`Error attaching to tmux session: ${e.message}`);
34
+ }
35
+ }
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.lsAction = lsAction;
4
+ const db_1 = require("../db");
5
+ const docker_1 = require("../docker");
6
+ const ui_1 = require("../utils/ui");
7
+ async function lsAction() {
8
+ const db = await (0, db_1.readDb)();
9
+ const runningContainers = await (0, docker_1.getRunningContainers)();
10
+ let output = '| Index/Session | Container Name | Creator | Uptime | Last Access |\n';
11
+ output += '|---------------|----------------------|---------|----------------------|-------------|\n';
12
+ db.containers.forEach((container, idx) => {
13
+ const dockerInfo = runningContainers.find(c => c.Id === container.id || c.Names.some(n => n.includes(container.name)));
14
+ const uptime = dockerInfo ? dockerInfo.Status : 'Stopped';
15
+ container.sessions.forEach(session => {
16
+ const lastAccess = new Date(session.lastAccess);
17
+ const today = new Date();
18
+ let lastAccessStr = lastAccess.toLocaleString();
19
+ if (lastAccess.toDateString() === today.toDateString()) {
20
+ lastAccessStr = `${lastAccess.getHours().toString().padStart(2, '0')}:${lastAccess.getMinutes().toString().padStart(2, '0')} Today`;
21
+ }
22
+ output += `| ${(idx + '/' + session.name).padEnd(13)} | ${container.name.padEnd(20)} | ${container.creator.padEnd(7)} | ${uptime.padEnd(20)} | ${lastAccessStr} |\n`;
23
+ });
24
+ });
25
+ (0, ui_1.renderBox)(output, 'Watchtower Environments');
26
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.stopAction = stopAction;
7
+ const db_1 = require("../db");
8
+ const execa_1 = require("execa");
9
+ const dockerode_1 = __importDefault(require("dockerode"));
10
+ const ui_1 = require("../utils/ui");
11
+ const docker = new dockerode_1.default();
12
+ async function stopAction(target) {
13
+ const parts = target.split('/');
14
+ const index = parseInt(parts[0], 10);
15
+ const sessionName = parts[1];
16
+ const db = await (0, db_1.readDb)();
17
+ const containerInfo = db.containers[index];
18
+ if (!containerInfo) {
19
+ (0, ui_1.logError)(`Container at index ${index} not found.`);
20
+ process.exit(1);
21
+ }
22
+ if (sessionName) {
23
+ try {
24
+ await (0, execa_1.execa)('docker', ['exec', containerInfo.id, 'tmux', 'kill-session', '-t', sessionName]);
25
+ await (0, db_1.removeSession)(index, sessionName);
26
+ (0, ui_1.logSuccess)(`Tmux session ${sessionName} in container ${containerInfo.name} stopped.`);
27
+ }
28
+ catch (e) {
29
+ (0, ui_1.logError)(`Error killing session ${sessionName}: ${e.message}`);
30
+ }
31
+ }
32
+ else {
33
+ try {
34
+ const container = docker.getContainer(containerInfo.id);
35
+ await container.stop();
36
+ await container.remove();
37
+ await (0, db_1.removeContainer)(containerInfo.name);
38
+ (0, ui_1.logSuccess)(`Container ${containerInfo.name} stopped and removed.`);
39
+ }
40
+ catch (e) {
41
+ (0, ui_1.logError)(`Error stopping container ${containerInfo.name}: ${e.message}`);
42
+ }
43
+ }
44
+ }
package/dist/db.js ADDED
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.initDb = initDb;
7
+ exports.readDb = readDb;
8
+ exports.writeDb = writeDb;
9
+ exports.updateContainerSession = updateContainerSession;
10
+ exports.addContainer = addContainer;
11
+ exports.removeContainer = removeContainer;
12
+ exports.removeSession = removeSession;
13
+ const fs_extra_1 = __importDefault(require("fs-extra"));
14
+ const path_1 = __importDefault(require("path"));
15
+ const os_1 = __importDefault(require("os"));
16
+ const zod_1 = require("zod");
17
+ const DB_DIR = path_1.default.join(os_1.default.homedir(), '.watchtower');
18
+ const DB_FILE = path_1.default.join(DB_DIR, 'db.json');
19
+ const SessionSchema = zod_1.z.object({
20
+ name: zod_1.z.string(),
21
+ lastAccess: zod_1.z.string(),
22
+ });
23
+ const ContainerSchema = zod_1.z.object({
24
+ id: zod_1.z.string(), // Docker container ID
25
+ name: zod_1.z.string(), // Watchtower container name
26
+ creator: zod_1.z.string(),
27
+ repoUrl: zod_1.z.string().optional(),
28
+ localDir: zod_1.z.string().optional(),
29
+ sessions: zod_1.z.array(SessionSchema),
30
+ });
31
+ const DatabaseSchema = zod_1.z.object({
32
+ containers: zod_1.z.array(ContainerSchema),
33
+ });
34
+ async function initDb() {
35
+ await fs_extra_1.default.ensureDir(DB_DIR);
36
+ if (!(await fs_extra_1.default.pathExists(DB_FILE))) {
37
+ await fs_extra_1.default.writeJson(DB_FILE, { containers: [] });
38
+ }
39
+ }
40
+ async function readDb() {
41
+ await initDb();
42
+ const data = await fs_extra_1.default.readJson(DB_FILE);
43
+ return DatabaseSchema.parse(data);
44
+ }
45
+ async function writeDb(db) {
46
+ await fs_extra_1.default.writeJson(DB_FILE, db, { spaces: 2 });
47
+ }
48
+ async function updateContainerSession(containerName, sessionName) {
49
+ const db = await readDb();
50
+ const container = db.containers.find(c => c.name === containerName);
51
+ if (container) {
52
+ const session = container.sessions.find(s => s.name === sessionName);
53
+ const now = new Date().toISOString();
54
+ if (session) {
55
+ session.lastAccess = now;
56
+ }
57
+ else {
58
+ container.sessions.push({ name: sessionName, lastAccess: now });
59
+ }
60
+ await writeDb(db);
61
+ }
62
+ }
63
+ async function addContainer(container) {
64
+ const db = await readDb();
65
+ db.containers.push(container);
66
+ await writeDb(db);
67
+ }
68
+ async function removeContainer(containerName) {
69
+ const db = await readDb();
70
+ db.containers = db.containers.filter(c => c.name !== containerName);
71
+ await writeDb(db);
72
+ }
73
+ async function removeSession(containerIndex, sessionName) {
74
+ const db = await readDb();
75
+ const container = db.containers[containerIndex];
76
+ if (container) {
77
+ container.sessions = container.sessions.filter(s => s.name !== sessionName);
78
+ await writeDb(db);
79
+ }
80
+ }
package/dist/docker.js ADDED
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getRunningContainers = getRunningContainers;
7
+ exports.buildImage = buildImage;
8
+ exports.createContainer = createContainer;
9
+ exports.ensureEnvironment = ensureEnvironment;
10
+ exports.setupDefaultTmux = setupDefaultTmux;
11
+ exports.cloneRepo = cloneRepo;
12
+ const dockerode_1 = __importDefault(require("dockerode"));
13
+ const execa_1 = require("execa");
14
+ const path_1 = __importDefault(require("path"));
15
+ const ora_1 = __importDefault(require("ora"));
16
+ const ui_1 = require("./utils/ui");
17
+ const docker = new dockerode_1.default();
18
+ async function getRunningContainers() {
19
+ return await docker.listContainers();
20
+ }
21
+ async function runInBox(title, cmd, args, options = {}) {
22
+ const dynamicBox = new ui_1.DynamicBox(title);
23
+ const subprocess = (0, execa_1.execa)(cmd, args, {
24
+ ...options,
25
+ all: true,
26
+ });
27
+ if (subprocess.all) {
28
+ subprocess.all.on('data', (data) => {
29
+ const lines = data.toString().split('\n');
30
+ lines.forEach((line) => dynamicBox.update(line));
31
+ });
32
+ }
33
+ try {
34
+ const result = await subprocess;
35
+ dynamicBox.stop();
36
+ return result;
37
+ }
38
+ catch (error) {
39
+ dynamicBox.stop();
40
+ throw error;
41
+ }
42
+ }
43
+ async function buildImage(dockerfilePath, repoName, contextDir) {
44
+ const imageName = `watchtower-${repoName.toLowerCase().replace(/[^a-z0-9]/g, '-')}`;
45
+ console.log(`Building image ${imageName}...`);
46
+ try {
47
+ await runInBox(`Building Image: ${imageName}`, 'docker', [
48
+ 'build', '-t', imageName, '-f', path_1.default.resolve(dockerfilePath), '.'
49
+ ], {
50
+ cwd: contextDir,
51
+ env: { ...process.env, DOCKER_BUILDKIT: '0' }
52
+ });
53
+ (0, ui_1.logSuccess)(`Image ${imageName} built successfully.`);
54
+ return imageName;
55
+ }
56
+ catch (error) {
57
+ (0, ui_1.logError)(`Failed to build image ${imageName}.`);
58
+ (0, ui_1.renderBox)(error.stderr || error.message, 'Docker Build Error', 'red');
59
+ process.exit(1);
60
+ }
61
+ }
62
+ async function createContainer(imageName, containerName, workspacePath) {
63
+ const spinner = (0, ora_1.default)(`Creating container ${containerName}...`).start();
64
+ try {
65
+ const container = await docker.createContainer({
66
+ Image: imageName,
67
+ name: containerName,
68
+ Tty: true,
69
+ OpenStdin: true,
70
+ HostConfig: {
71
+ Binds: [`${path_1.default.resolve(workspacePath)}:/workspace`],
72
+ },
73
+ WorkingDir: '/workspace',
74
+ Cmd: ['/bin/bash'],
75
+ });
76
+ await container.start();
77
+ spinner.succeed(`Container ${containerName} started.`);
78
+ return container;
79
+ }
80
+ catch (error) {
81
+ spinner.fail(`Failed to create or start container ${containerName}.`);
82
+ (0, ui_1.renderBox)(error.json?.message || error.message, 'Docker Container Error', 'red');
83
+ process.exit(1);
84
+ }
85
+ }
86
+ async function ensureEnvironment(containerId) {
87
+ const container = docker.getContainer(containerId);
88
+ console.log('Configuring environment inside container...');
89
+ const setupCommands = [
90
+ { name: 'Node.js', cmd: 'which node || (apt-get update && apt-get install -y curl && curl -fsSL https://deb.nodesource.com/setup_current.x | bash - && apt-get install -y nodejs)' },
91
+ { name: 'Tmux', cmd: 'which tmux || (apt-get update && apt-get install -y tmux)' },
92
+ { name: 'Gemini CLI', cmd: 'which gemini || npm install -g @google/gemini-cli' },
93
+ ];
94
+ for (const item of setupCommands) {
95
+ try {
96
+ await runInBox(`Installing ${item.name}`, 'docker', ['exec', containerId, 'bash', '-c', item.cmd]);
97
+ }
98
+ catch (error) {
99
+ (0, ui_1.logError)(`Failed to install ${item.name}.`);
100
+ (0, ui_1.renderBox)(error.message, 'Environment Setup Error', 'red');
101
+ process.exit(1);
102
+ }
103
+ }
104
+ (0, ui_1.logSuccess)('Environment configured successfully.');
105
+ }
106
+ async function setupDefaultTmux(containerId) {
107
+ const spinner = (0, ora_1.default)('Starting default tmux session...').start();
108
+ try {
109
+ const container = docker.getContainer(containerId);
110
+ const exec = await container.exec({
111
+ Cmd: ['tmux', 'new-session', '-d', '-s', 'default'],
112
+ });
113
+ await exec.start({});
114
+ spinner.succeed('Default tmux session started.');
115
+ }
116
+ catch (error) {
117
+ spinner.fail('Failed to start default tmux session.');
118
+ process.exit(1);
119
+ }
120
+ }
121
+ async function cloneRepo(repoUrl, targetDir) {
122
+ console.log(`Cloning repository ${repoUrl}...`);
123
+ try {
124
+ await runInBox(`Cloning Repository`, 'git', ['clone', '--recursive', repoUrl, targetDir]);
125
+ (0, ui_1.logSuccess)('Repository cloned successfully.');
126
+ }
127
+ catch (error) {
128
+ (0, ui_1.logError)(`Failed to clone repository ${repoUrl}.`);
129
+ (0, ui_1.renderBox)(error.stderr || error.message, 'Git Clone Error', 'red');
130
+ process.exit(1);
131
+ }
132
+ }
package/dist/index.js ADDED
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const commander_1 = require("commander");
5
+ const create_1 = require("./commands/create");
6
+ const enter_1 = require("./commands/enter");
7
+ const ls_1 = require("./commands/ls");
8
+ const stop_1 = require("./commands/stop");
9
+ const { version } = require('../package.json');
10
+ const program = new commander_1.Command();
11
+ const LOGO = `
12
+
13
+ ▄▄▄▄ ▄▄▄ ▄▄▄▄ ▄▄ ▄▄▄▄▄▄▄▄▄
14
+ ▀███ ███ ███▀ ██ ██ ▀▀▀███▀▀▀
15
+ ███ ███ ███ ▀▀█▄ ▀██▀▀ ▄████ ████▄ ███ ▄███▄ ██ ██ ▄█▀█▄ ████▄
16
+ ███▄▄███▄▄███ ▄█▀██ ██ ██ ██ ██ ███ ██ ██ ██ █ ██ ██▄█▀ ██ ▀▀
17
+ ▀████▀████▀ ▀█▄██ ██ ▀████ ██ ██ ███ ▀███▀ ██▀██ ▀█▄▄▄ ██
18
+
19
+ `;
20
+ program
21
+ .name('gwatch-cli')
22
+ .description('Persistent development environments wrapper for Docker and Tmux')
23
+ .version(version)
24
+ .addHelpText('before', LOGO);
25
+ program
26
+ .command('create')
27
+ .description('Create a container from a remote git repo or local directory')
28
+ .option('--repo <url>', 'Git repository URL')
29
+ .option('--dir <path>', 'Local directory path')
30
+ .option('--dockerfile <path>', 'Path to Dockerfile', './Dockerfile')
31
+ .option('--name <name>', 'Optional name override')
32
+ .action(create_1.createAction);
33
+ program
34
+ .command('enter <target>')
35
+ .description('Enter a container/session (e.g., 0/default)')
36
+ .action(enter_1.enterAction);
37
+ program
38
+ .command('ls')
39
+ .description('List containers and sessions')
40
+ .action(ls_1.lsAction);
41
+ program
42
+ .command('stop <target>')
43
+ .description('Stop a container or a specific tmux session')
44
+ .action(stop_1.stopAction);
45
+ // Logic to show logo and help if no command is provided
46
+ if (process.argv.length === 2) {
47
+ program.help();
48
+ }
49
+ // Logic to show logo on version
50
+ if (process.argv.includes('-V') || process.argv.includes('--version')) {
51
+ console.log(LOGO);
52
+ }
53
+ program.parse(process.argv);
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DynamicBox = void 0;
7
+ exports.renderBox = renderBox;
8
+ exports.logInfo = logInfo;
9
+ exports.logSuccess = logSuccess;
10
+ exports.logError = logError;
11
+ const boxen_1 = __importDefault(require("boxen"));
12
+ const chalk_1 = __importDefault(require("chalk"));
13
+ const log_update_1 = __importDefault(require("log-update"));
14
+ function renderBox(content, title, color = 'cyan') {
15
+ console.log((0, boxen_1.default)(content, {
16
+ padding: 1,
17
+ margin: 1,
18
+ borderStyle: 'round',
19
+ borderColor: color,
20
+ title: title ? chalk_1.default.bold(title) : undefined,
21
+ titleAlignment: 'left',
22
+ }));
23
+ }
24
+ function logInfo(message) {
25
+ console.log(chalk_1.default.blue('ℹ ') + message);
26
+ }
27
+ function logSuccess(message) {
28
+ console.log(chalk_1.default.green('✔ ') + message);
29
+ }
30
+ function logError(message) {
31
+ console.log(chalk_1.default.red('▀ ') + message);
32
+ }
33
+ class DynamicBox {
34
+ lines = [];
35
+ maxLines;
36
+ title;
37
+ color;
38
+ constructor(title, maxLines = 10, color = 'cyan') {
39
+ this.title = title;
40
+ this.maxLines = maxLines;
41
+ this.color = color;
42
+ }
43
+ update(line) {
44
+ if (!line.trim())
45
+ return;
46
+ this.lines.push(line.trim());
47
+ if (this.lines.length > this.maxLines) {
48
+ this.lines.shift();
49
+ }
50
+ this.render();
51
+ }
52
+ render() {
53
+ const content = this.lines.join('\n');
54
+ (0, log_update_1.default)((0, boxen_1.default)(content, {
55
+ padding: { left: 1, right: 1, top: 0, bottom: 0 },
56
+ margin: 1,
57
+ borderStyle: 'round',
58
+ borderColor: this.color,
59
+ title: chalk_1.default.bold(this.title),
60
+ titleAlignment: 'left',
61
+ }));
62
+ }
63
+ stop() {
64
+ log_update_1.default.done();
65
+ }
66
+ }
67
+ exports.DynamicBox = DynamicBox;
package/docs/logo.jpg ADDED
Binary file
Binary file
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@gwatch/gwatch-cli",
3
+ "version": "1.0.0",
4
+ "description": "Watchtower: Persistent development environments wrapper for Docker and Tmux",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "gwatch-cli": "dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "start": "ts-node src/index.ts",
12
+ "watch": "tsc -w",
13
+ "prepublishOnly": "npm run build"
14
+ },
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/G-Watch/Watchtower.git"
21
+ },
22
+ "keywords": [],
23
+ "author": "",
24
+ "license": "ISC",
25
+ "bugs": {
26
+ "url": "https://github.com/G-Watch/Watchtower/issues"
27
+ },
28
+ "homepage": "https://github.com/G-Watch/Watchtower#readme",
29
+ "dependencies": {
30
+ "boxen": "5.1.2",
31
+ "chalk": "4.1.2",
32
+ "commander": "^14.0.3",
33
+ "dockerode": "^4.0.9",
34
+ "execa": "^9.6.1",
35
+ "fs-extra": "^11.3.3",
36
+ "log-update": "4.0.0",
37
+ "ora": "5.4.1",
38
+ "zod": "^4.3.6"
39
+ },
40
+ "devDependencies": {
41
+ "@types/chalk": "^2.2.0",
42
+ "@types/dockerode": "^4.0.1",
43
+ "@types/fs-extra": "^11.0.4",
44
+ "@types/log-update": "^3.0.0",
45
+ "@types/node": "^25.2.2",
46
+ "@types/ora": "^5.2.0",
47
+ "ts-node": "^10.9.2",
48
+ "typescript": "^5.9.3"
49
+ }
50
+ }