@tenux/cli 0.0.17 → 0.0.18

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 (2) hide show
  1. package/dist/cli.js +400 -85
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -13,46 +13,361 @@ import {
13
13
 
14
14
  // src/cli.ts
15
15
  import { Command } from "commander";
16
- import chalk from "chalk";
16
+ import chalk2 from "chalk";
17
17
  import { hostname } from "os";
18
18
  import { homedir } from "os";
19
- import { join, dirname } from "path";
20
- import { existsSync, readFileSync } from "fs";
21
- import { execSync } from "child_process";
19
+ import { join as join2, dirname } from "path";
20
+ import { existsSync as existsSync2, readFileSync } from "fs";
21
+ import { execSync as execSync2 } from "child_process";
22
22
  import { fileURLToPath } from "url";
23
23
  import { createClient } from "@supabase/supabase-js";
24
+
25
+ // src/handlers/project.ts
26
+ import { spawn, execSync } from "child_process";
27
+ import { resolve, join } from "path";
28
+ import { existsSync, mkdirSync, readdirSync, statSync } from "fs";
29
+ import chalk from "chalk";
30
+ var LOCKFILE_PM = {
31
+ "bun.lockb": "bun",
32
+ "bun.lock": "bun",
33
+ "pnpm-lock.yaml": "pnpm",
34
+ "yarn.lock": "yarn",
35
+ "package-lock.json": "npm",
36
+ "requirements.txt": "pip",
37
+ "go.mod": "go",
38
+ "Cargo.toml": "cargo"
39
+ };
40
+ function detectPackageManager(dir) {
41
+ for (const [file, pm] of Object.entries(LOCKFILE_PM)) {
42
+ if (existsSync(join(dir, file))) return pm;
43
+ }
44
+ if (existsSync(join(dir, "package.json"))) return "npm";
45
+ return null;
46
+ }
47
+ function installCommand(pm) {
48
+ switch (pm) {
49
+ case "bun":
50
+ return ["bun", "install"];
51
+ case "pnpm":
52
+ return ["pnpm", "install"];
53
+ case "yarn":
54
+ return ["yarn"];
55
+ case "npm":
56
+ return ["npm", "install"];
57
+ case "pip":
58
+ return ["pip", "install", "-r", "requirements.txt"];
59
+ case "cargo":
60
+ return ["cargo", "build"];
61
+ case "go":
62
+ return ["go", "mod", "download"];
63
+ default:
64
+ return ["npm", "install"];
65
+ }
66
+ }
67
+ function run(cmd, args, cwd, timeoutMs = 3e5) {
68
+ return new Promise((resolve2, reject) => {
69
+ const isWindows = process.platform === "win32";
70
+ const proc = spawn(cmd, args, {
71
+ cwd,
72
+ shell: isWindows,
73
+ stdio: ["ignore", "pipe", "pipe"],
74
+ timeout: timeoutMs
75
+ });
76
+ let stdout = "";
77
+ let stderr = "";
78
+ proc.stdout.on("data", (d) => {
79
+ stdout += d.toString();
80
+ });
81
+ proc.stderr.on("data", (d) => {
82
+ stderr += d.toString();
83
+ });
84
+ proc.on("close", (code) => {
85
+ if (code === 0) resolve2(stdout);
86
+ else reject(new Error(stderr.trim().slice(0, 500) || `Exit code ${code}`));
87
+ });
88
+ proc.on("error", reject);
89
+ });
90
+ }
91
+ async function updateProject(supabase, projectId, fields) {
92
+ await supabase.from("projects").update({ ...fields, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", projectId);
93
+ }
94
+ async function handleProjectClone(command, supabase) {
95
+ const { url, name, project_id, github_token } = command.payload;
96
+ const config = loadConfig();
97
+ if (!existsSync(config.projectsDir)) mkdirSync(config.projectsDir, { recursive: true });
98
+ const targetDir = resolve(config.projectsDir, name);
99
+ await updateProject(supabase, project_id, { status: "cloning" });
100
+ let cloneUrl = url;
101
+ if (github_token && url.startsWith("https://github.com/")) {
102
+ cloneUrl = url.replace("https://github.com/", `https://${github_token}@github.com/`);
103
+ }
104
+ console.log(chalk.blue("\u2192"), `Cloning ${url} \u2192 ${name}`);
105
+ try {
106
+ await run("git", ["clone", cloneUrl, targetDir], config.projectsDir, 3e5);
107
+ } catch (err) {
108
+ const msg = err instanceof Error ? err.message : String(err);
109
+ console.log(chalk.red("\u2717"), `Clone failed: ${msg}`);
110
+ await updateProject(supabase, project_id, {
111
+ status: "error",
112
+ error_message: msg
113
+ });
114
+ throw err;
115
+ }
116
+ const pm = detectPackageManager(targetDir);
117
+ await updateProject(supabase, project_id, {
118
+ status: "installing",
119
+ package_manager: pm,
120
+ local_path: targetDir,
121
+ path: name
122
+ });
123
+ if (pm) {
124
+ console.log(chalk.blue("\u2192"), `Installing with ${pm}\u2026`);
125
+ try {
126
+ const [cmd, ...args] = installCommand(pm);
127
+ await run(cmd, args, targetDir, 6e5);
128
+ } catch (err) {
129
+ const msg = err instanceof Error ? err.message : String(err);
130
+ console.log(chalk.yellow("!"), `Install warning: ${msg}`);
131
+ }
132
+ }
133
+ await updateProject(supabase, project_id, { status: "ready" });
134
+ console.log(chalk.green("\u2713"), `Project ${name} ready`);
135
+ await supabase.from("commands").update({
136
+ status: "done",
137
+ result: { path: name, package_manager: pm },
138
+ completed_at: (/* @__PURE__ */ new Date()).toISOString()
139
+ }).eq("id", command.id);
140
+ }
141
+ var TEMPLATES = {
142
+ nextjs: {
143
+ cmd: "npx",
144
+ args: (name) => ["create-next-app@latest", name, "--yes"],
145
+ pm: "npm"
146
+ },
147
+ "react-vite": {
148
+ cmd: "npm",
149
+ args: (name) => ["create", "vite@latest", name, "--", "--template", "react-ts"],
150
+ pm: "npm"
151
+ },
152
+ express: {
153
+ cmd: "npx",
154
+ args: (name) => ["express-generator", name],
155
+ pm: "npm"
156
+ },
157
+ "node-ts": {
158
+ cmd: "npx",
159
+ args: (name) => ["create-ts-node", name, "--yes"],
160
+ pm: "npm"
161
+ }
162
+ };
163
+ async function handleProjectCreate(command, supabase) {
164
+ const { template, name, project_id } = command.payload;
165
+ const config = loadConfig();
166
+ if (!existsSync(config.projectsDir)) mkdirSync(config.projectsDir, { recursive: true });
167
+ const tmpl = TEMPLATES[template];
168
+ if (!tmpl) {
169
+ await updateProject(supabase, project_id, {
170
+ status: "error",
171
+ error_message: `Unknown template: ${template}`
172
+ });
173
+ throw new Error(`Unknown template: ${template}`);
174
+ }
175
+ await updateProject(supabase, project_id, { status: "cloning" });
176
+ console.log(chalk.blue("\u2192"), `Creating ${template} project: ${name}`);
177
+ try {
178
+ const isWindows = process.platform === "win32";
179
+ const cmd = isWindows && tmpl.cmd === "npx" ? "npx.cmd" : tmpl.cmd;
180
+ await run(cmd, tmpl.args(name), config.projectsDir, 3e5);
181
+ } catch (err) {
182
+ const msg = err instanceof Error ? err.message : String(err);
183
+ console.log(chalk.red("\u2717"), `Template creation failed: ${msg}`);
184
+ await updateProject(supabase, project_id, {
185
+ status: "error",
186
+ error_message: msg
187
+ });
188
+ throw err;
189
+ }
190
+ const targetDir = resolve(config.projectsDir, name);
191
+ const pm = detectPackageManager(targetDir) || tmpl.pm || "npm";
192
+ await updateProject(supabase, project_id, {
193
+ status: "installing",
194
+ package_manager: pm,
195
+ local_path: targetDir,
196
+ path: name
197
+ });
198
+ if (pm && existsSync(join(targetDir, "package.json"))) {
199
+ try {
200
+ const [installCmd, ...installArgs] = installCommand(pm);
201
+ await run(installCmd, installArgs, targetDir, 6e5);
202
+ } catch {
203
+ }
204
+ }
205
+ await updateProject(supabase, project_id, { status: "ready" });
206
+ console.log(chalk.green("\u2713"), `Project ${name} ready`);
207
+ await supabase.from("commands").update({
208
+ status: "done",
209
+ result: { path: name, package_manager: pm, template },
210
+ completed_at: (/* @__PURE__ */ new Date()).toISOString()
211
+ }).eq("id", command.id);
212
+ }
213
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
214
+ "node_modules",
215
+ ".git",
216
+ "__pycache__",
217
+ ".next",
218
+ "dist",
219
+ "build",
220
+ ".turbo",
221
+ ".cache",
222
+ ".vercel",
223
+ ".output",
224
+ "coverage",
225
+ ".svelte-kit",
226
+ "target",
227
+ "venv",
228
+ ".venv",
229
+ "env",
230
+ ".env"
231
+ ]);
232
+ function buildTree(dir, depth, maxEntries, count) {
233
+ if (depth <= 0 || count.n >= maxEntries) return [];
234
+ let entries;
235
+ try {
236
+ entries = readdirSync(dir).sort();
237
+ } catch {
238
+ return [];
239
+ }
240
+ const nodes = [];
241
+ for (const entry of entries) {
242
+ if (count.n >= maxEntries) break;
243
+ if (entry.startsWith(".") && entry !== ".env.example" && entry !== ".gitignore") continue;
244
+ const full = join(dir, entry);
245
+ let stat;
246
+ try {
247
+ stat = statSync(full);
248
+ } catch {
249
+ continue;
250
+ }
251
+ count.n++;
252
+ if (stat.isDirectory()) {
253
+ if (SKIP_DIRS.has(entry)) {
254
+ nodes.push({ name: entry, type: "dir" });
255
+ continue;
256
+ }
257
+ const children = buildTree(full, depth - 1, maxEntries, count);
258
+ nodes.push({ name: entry, type: "dir", children });
259
+ } else {
260
+ nodes.push({ name: entry, type: "file" });
261
+ }
262
+ }
263
+ return nodes;
264
+ }
265
+ async function handleProjectTree(command, supabase) {
266
+ const { path: projectPath, depth = 4 } = command.payload;
267
+ const config = loadConfig();
268
+ const fullPath = resolve(config.projectsDir, projectPath);
269
+ if (!existsSync(fullPath)) {
270
+ await supabase.from("commands").update({
271
+ status: "error",
272
+ result: { error: `Project path not found: ${projectPath}` },
273
+ completed_at: (/* @__PURE__ */ new Date()).toISOString()
274
+ }).eq("id", command.id);
275
+ return;
276
+ }
277
+ const count = { n: 0 };
278
+ const tree = buildTree(fullPath, depth, 500, count);
279
+ await supabase.from("commands").update({
280
+ status: "done",
281
+ result: { tree, total: count.n },
282
+ completed_at: (/* @__PURE__ */ new Date()).toISOString()
283
+ }).eq("id", command.id);
284
+ }
285
+ async function handleProjectGitStatus(command, supabase) {
286
+ const { path: projectPath } = command.payload;
287
+ const config = loadConfig();
288
+ const fullPath = resolve(config.projectsDir, projectPath);
289
+ if (!existsSync(fullPath)) {
290
+ await supabase.from("commands").update({
291
+ status: "error",
292
+ result: { error: `Project path not found: ${projectPath}` },
293
+ completed_at: (/* @__PURE__ */ new Date()).toISOString()
294
+ }).eq("id", command.id);
295
+ return;
296
+ }
297
+ try {
298
+ let branch = "unknown";
299
+ try {
300
+ branch = execSync("git rev-parse --abbrev-ref HEAD", { cwd: fullPath }).toString().trim();
301
+ } catch {
302
+ }
303
+ let files = [];
304
+ try {
305
+ const raw = execSync("git status --porcelain", { cwd: fullPath }).toString();
306
+ files = raw.split("\n").filter(Boolean).map((line) => {
307
+ const x = line[0];
308
+ const y = line[1];
309
+ const file = line.slice(3);
310
+ const staged = x !== " " && x !== "?";
311
+ return { file, status: line.slice(0, 2).trim(), staged };
312
+ });
313
+ } catch {
314
+ }
315
+ let ahead = 0;
316
+ let behind = 0;
317
+ try {
318
+ const ab = execSync("git rev-list --left-right --count HEAD...@{upstream}", { cwd: fullPath }).toString().trim().split(/\s+/);
319
+ ahead = parseInt(ab[0]) || 0;
320
+ behind = parseInt(ab[1]) || 0;
321
+ } catch {
322
+ }
323
+ await supabase.from("commands").update({
324
+ status: "done",
325
+ result: { branch, files, ahead, behind, clean: files.length === 0 },
326
+ completed_at: (/* @__PURE__ */ new Date()).toISOString()
327
+ }).eq("id", command.id);
328
+ } catch (err) {
329
+ const msg = err instanceof Error ? err.message : String(err);
330
+ await supabase.from("commands").update({
331
+ status: "error",
332
+ result: { error: msg },
333
+ completed_at: (/* @__PURE__ */ new Date()).toISOString()
334
+ }).eq("id", command.id);
335
+ }
336
+ }
337
+
338
+ // src/cli.ts
24
339
  var __dirname = dirname(fileURLToPath(import.meta.url));
25
- var pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
340
+ var pkg = JSON.parse(readFileSync(join2(__dirname, "..", "package.json"), "utf-8"));
26
341
  var program = new Command();
27
342
  program.name("tenux").description("Tenux desktop agent \u2014 bridges your machine to the Tenux mobile IDE").version(pkg.version);
28
343
  function detectClaudeCode() {
29
344
  let installed = false;
30
345
  try {
31
346
  if (process.platform === "win32") {
32
- execSync("where claude", { stdio: "ignore" });
347
+ execSync2("where claude", { stdio: "ignore" });
33
348
  } else {
34
- execSync("which claude", { stdio: "ignore" });
349
+ execSync2("which claude", { stdio: "ignore" });
35
350
  }
36
351
  installed = true;
37
352
  } catch {
38
353
  installed = false;
39
354
  }
40
- const authenticated = existsSync(join(homedir(), ".claude"));
355
+ const authenticated = existsSync2(join2(homedir(), ".claude"));
41
356
  return { installed, authenticated };
42
357
  }
43
358
  async function sleep(ms) {
44
- return new Promise((resolve) => setTimeout(resolve, ms));
359
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
45
360
  }
46
361
  program.command("login").description("Authenticate with Tenux via browser-based device auth").option("--url <url>", "Tenux app URL").action(async (opts) => {
47
- console.log(chalk.bold("\n tenux"), chalk.dim("desktop agent\n"));
362
+ console.log(chalk2.bold("\n tenux"), chalk2.dim("desktop agent\n"));
48
363
  const appUrl = (opts.url ?? process.env.TENUX_APP_URL ?? "https://tenux.dev").replace(/\/+$/, "");
49
- console.log(chalk.dim(" App:"), appUrl);
364
+ console.log(chalk2.dim(" App:"), appUrl);
50
365
  const deviceName = hostname();
51
366
  const platform = process.platform;
52
- console.log(chalk.dim(" Device:"), deviceName);
53
- console.log(chalk.dim(" Platform:"), platform);
367
+ console.log(chalk2.dim(" Device:"), deviceName);
368
+ console.log(chalk2.dim(" Platform:"), platform);
54
369
  console.log();
55
- console.log(chalk.dim(" Requesting device code..."));
370
+ console.log(chalk2.dim(" Requesting device code..."));
56
371
  let code;
57
372
  let expiresAt;
58
373
  let supabaseUrl;
@@ -69,7 +384,7 @@ program.command("login").description("Authenticate with Tenux via browser-based
69
384
  });
70
385
  if (!res.ok) {
71
386
  const text = await res.text();
72
- console.log(chalk.red(" \u2717"), `Failed to get device code: ${res.status} ${text}`);
387
+ console.log(chalk2.red(" \u2717"), `Failed to get device code: ${res.status} ${text}`);
73
388
  process.exit(1);
74
389
  }
75
390
  const data = await res.json();
@@ -79,26 +394,26 @@ program.command("login").description("Authenticate with Tenux via browser-based
79
394
  supabaseAnonKey = data.supabase_anon_key;
80
395
  } catch (err) {
81
396
  console.log(
82
- chalk.red(" \u2717"),
397
+ chalk2.red(" \u2717"),
83
398
  `Could not reach ${appUrl}: ${err instanceof Error ? err.message : String(err)}`
84
399
  );
85
400
  process.exit(1);
86
401
  }
87
402
  console.log();
88
- console.log(chalk.bold.cyan(` \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510`));
89
- console.log(chalk.bold.cyan(` \u2502 \u2502`));
90
- console.log(chalk.bold.cyan(` \u2502 Code: ${chalk.white.bold(code)} \u2502`));
91
- console.log(chalk.bold.cyan(` \u2502 \u2502`));
92
- console.log(chalk.bold.cyan(` \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518`));
403
+ console.log(chalk2.bold.cyan(` \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510`));
404
+ console.log(chalk2.bold.cyan(` \u2502 \u2502`));
405
+ console.log(chalk2.bold.cyan(` \u2502 Code: ${chalk2.white.bold(code)} \u2502`));
406
+ console.log(chalk2.bold.cyan(` \u2502 \u2502`));
407
+ console.log(chalk2.bold.cyan(` \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518`));
93
408
  console.log();
94
409
  const linkUrl = `${appUrl}/link?code=${code}`;
95
- console.log(chalk.dim(" Opening browser to:"), chalk.underline(linkUrl));
410
+ console.log(chalk2.dim(" Opening browser to:"), chalk2.underline(linkUrl));
96
411
  try {
97
412
  const open = (await import("open")).default;
98
413
  await open(linkUrl);
99
414
  } catch {
100
- console.log(chalk.yellow(" !"), "Could not open browser automatically.");
101
- console.log(chalk.dim(" Open this URL manually:"), chalk.underline(linkUrl));
415
+ console.log(chalk2.yellow(" !"), "Could not open browser automatically.");
416
+ console.log(chalk2.dim(" Open this URL manually:"), chalk2.underline(linkUrl));
102
417
  }
103
418
  console.log();
104
419
  const dots = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
@@ -110,11 +425,11 @@ program.command("login").description("Authenticate with Tenux via browser-based
110
425
  const expiresTime = new Date(expiresAt).getTime();
111
426
  while (!approved) {
112
427
  if (Date.now() > expiresTime) {
113
- console.log(chalk.red("\n \u2717"), "Device code expired. Please run `tenux login` again.");
428
+ console.log(chalk2.red("\n \u2717"), "Device code expired. Please run `tenux login` again.");
114
429
  process.exit(1);
115
430
  }
116
431
  process.stdout.write(
117
- `\r ${chalk.cyan(dots[dotIndex % dots.length])} Waiting for approval...`
432
+ `\r ${chalk2.cyan(dots[dotIndex % dots.length])} Waiting for approval...`
118
433
  );
119
434
  dotIndex++;
120
435
  await sleep(2e3);
@@ -132,16 +447,16 @@ program.command("login").description("Authenticate with Tenux via browser-based
132
447
  userId = data.user_id || "";
133
448
  approved = true;
134
449
  } else if (data.status === "expired") {
135
- console.log(chalk.red("\n \u2717"), "Device code expired. Please run `tenux login` again.");
450
+ console.log(chalk2.red("\n \u2717"), "Device code expired. Please run `tenux login` again.");
136
451
  process.exit(1);
137
452
  }
138
453
  } catch {
139
454
  }
140
455
  }
141
456
  process.stdout.write("\r" + " ".repeat(60) + "\r");
142
- console.log(chalk.green(" \u2713"), "Device approved!");
457
+ console.log(chalk2.green(" \u2713"), "Device approved!");
143
458
  console.log();
144
- console.log(chalk.dim(" Exchanging token for session..."));
459
+ console.log(chalk2.dim(" Exchanging token for session..."));
145
460
  let accessToken = "";
146
461
  let refreshToken = "";
147
462
  try {
@@ -153,11 +468,11 @@ program.command("login").description("Authenticate with Tenux via browser-based
153
468
  type: "magiclink"
154
469
  });
155
470
  if (error) {
156
- console.log(chalk.red(" \u2717"), `Auth failed: ${error.message}`);
471
+ console.log(chalk2.red(" \u2717"), `Auth failed: ${error.message}`);
157
472
  process.exit(1);
158
473
  }
159
474
  if (!session.session) {
160
- console.log(chalk.red(" \u2717"), "No session returned from auth exchange.");
475
+ console.log(chalk2.red(" \u2717"), "No session returned from auth exchange.");
161
476
  process.exit(1);
162
477
  }
163
478
  accessToken = session.session.access_token;
@@ -165,12 +480,12 @@ program.command("login").description("Authenticate with Tenux via browser-based
165
480
  userId = session.session.user?.id || userId;
166
481
  } catch (err) {
167
482
  console.log(
168
- chalk.red(" \u2717"),
483
+ chalk2.red(" \u2717"),
169
484
  `Token exchange failed: ${err instanceof Error ? err.message : String(err)}`
170
485
  );
171
486
  process.exit(1);
172
487
  }
173
- console.log(chalk.green(" \u2713"), "Session established.");
488
+ console.log(chalk2.green(" \u2713"), "Session established.");
174
489
  const config = {
175
490
  appUrl,
176
491
  supabaseUrl,
@@ -180,69 +495,69 @@ program.command("login").description("Authenticate with Tenux via browser-based
180
495
  deviceId,
181
496
  deviceName,
182
497
  userId,
183
- projectsDir: join(homedir(), ".tenux", "projects")
498
+ projectsDir: join2(homedir(), ".tenux", "projects")
184
499
  };
185
500
  saveConfig(config);
186
- console.log(chalk.green(" \u2713"), "Config saved to", chalk.dim(getConfigPath()));
501
+ console.log(chalk2.green(" \u2713"), "Config saved to", chalk2.dim(getConfigPath()));
187
502
  console.log();
188
503
  const claude = detectClaudeCode();
189
504
  if (claude.installed) {
190
- console.log(chalk.green(" \u2713"), "Claude Code detected");
505
+ console.log(chalk2.green(" \u2713"), "Claude Code detected");
191
506
  if (claude.authenticated) {
192
- console.log(chalk.green(" \u2713"), "Claude Code authenticated (~/.claude exists)");
507
+ console.log(chalk2.green(" \u2713"), "Claude Code authenticated (~/.claude exists)");
193
508
  } else {
194
509
  console.log(
195
- chalk.yellow(" !"),
510
+ chalk2.yellow(" !"),
196
511
  "Claude Code found but ~/.claude not detected.",
197
- chalk.dim("Run `claude login` to authenticate.")
512
+ chalk2.dim("Run `claude login` to authenticate.")
198
513
  );
199
514
  }
200
515
  } else {
201
- console.log(chalk.yellow(" !"), "Claude Code not found.");
516
+ console.log(chalk2.yellow(" !"), "Claude Code not found.");
202
517
  console.log(
203
- chalk.dim(" Install it:"),
204
- chalk.underline("https://docs.anthropic.com/en/docs/claude-code")
518
+ chalk2.dim(" Install it:"),
519
+ chalk2.underline("https://docs.anthropic.com/en/docs/claude-code")
205
520
  );
206
521
  console.log(
207
- chalk.dim(" Then run:"),
208
- chalk.cyan("claude login")
522
+ chalk2.dim(" Then run:"),
523
+ chalk2.cyan("claude login")
209
524
  );
210
525
  }
211
526
  console.log();
212
- console.log(chalk.dim(" Device:"), deviceName);
213
- console.log(chalk.dim(" Device ID:"), deviceId);
214
- console.log(chalk.dim(" User ID:"), userId);
215
- console.log(chalk.dim(" Projects:"), config.projectsDir);
527
+ console.log(chalk2.dim(" Device:"), deviceName);
528
+ console.log(chalk2.dim(" Device ID:"), deviceId);
529
+ console.log(chalk2.dim(" User ID:"), userId);
530
+ console.log(chalk2.dim(" Projects:"), config.projectsDir);
216
531
  console.log();
217
532
  console.log(
218
- chalk.dim(" Next: start the agent:"),
219
- chalk.cyan("tenux start")
533
+ chalk2.dim(" Next: start the agent:"),
534
+ chalk2.cyan("tenux start")
220
535
  );
221
536
  console.log();
222
537
  });
223
538
  program.command("start").description("Start the agent and listen for commands").action(async () => {
224
539
  if (!configExists()) {
225
- console.log(chalk.red("\u2717"), "Not logged in. Run `tenux login` first.");
540
+ console.log(chalk2.red("\u2717"), "Not logged in. Run `tenux login` first.");
226
541
  process.exit(1);
227
542
  }
228
543
  const claude = detectClaudeCode();
229
544
  if (!claude.installed) {
230
- console.log(chalk.red("\u2717"), "Claude Code not found.");
231
- console.log(chalk.dim(" Install it:"), chalk.cyan("npm i -g @anthropic-ai/claude-code"));
232
- console.log(chalk.dim(" Then run:"), chalk.cyan("claude login"));
545
+ console.log(chalk2.red("\u2717"), "Claude Code not found.");
546
+ console.log(chalk2.dim(" Install it:"), chalk2.cyan("npm i -g @anthropic-ai/claude-code"));
547
+ console.log(chalk2.dim(" Then run:"), chalk2.cyan("claude login"));
233
548
  process.exit(1);
234
549
  }
235
550
  const config = loadConfig();
236
- console.log(chalk.bold("\n tenux"), chalk.dim("desktop agent\n"));
237
- console.log(chalk.dim(" Device:"), config.deviceName);
238
- console.log(chalk.dim(" Projects:"), config.projectsDir);
551
+ console.log(chalk2.bold("\n tenux"), chalk2.dim("desktop agent\n"));
552
+ console.log(chalk2.dim(" Device:"), config.deviceName);
553
+ console.log(chalk2.dim(" Projects:"), config.projectsDir);
239
554
  console.log();
240
555
  const supabase = getSupabase();
241
556
  try {
242
557
  await initSupabaseSession();
243
558
  } catch (err) {
244
- console.log(chalk.red("\u2717"), `Session expired: ${err.message}`);
245
- console.log(chalk.dim(" Run `tenux login` to re-authenticate."));
559
+ console.log(chalk2.red("\u2717"), `Session expired: ${err.message}`);
560
+ console.log(chalk2.dim(" Run `tenux login` to re-authenticate."));
246
561
  process.exit(1);
247
562
  }
248
563
  await supabase.from("devices").update({
@@ -253,53 +568,53 @@ program.command("start").description("Start the agent and listen for commands").
253
568
  const heartbeat = setInterval(async () => {
254
569
  const { error } = await supabase.from("devices").update({ last_seen_at: (/* @__PURE__ */ new Date()).toISOString(), is_online: true }).eq("id", config.deviceId);
255
570
  if (error) {
256
- console.log(chalk.red("\u2717"), chalk.dim(`Heartbeat failed: ${error.message}`));
571
+ console.log(chalk2.red("\u2717"), chalk2.dim(`Heartbeat failed: ${error.message}`));
257
572
  }
258
573
  }, 3e4);
259
574
  const relay = new Relay(supabase);
260
575
  const shutdown = async () => {
261
- console.log(chalk.dim("\n Shutting down..."));
576
+ console.log(chalk2.dim("\n Shutting down..."));
262
577
  clearInterval(heartbeat);
263
578
  await relay.stop();
264
579
  await supabase.from("devices").update({ is_online: false }).eq("id", config.deviceId);
265
580
  process.exit(0);
266
581
  };
267
- relay.on("claude.query", handleClaudeQuery).on("agent.shutdown", async () => {
268
- console.log(chalk.yellow("\n \u26A1 Remote shutdown requested"));
582
+ relay.on("claude.query", handleClaudeQuery).on("project.clone", handleProjectClone).on("project.create", handleProjectCreate).on("project.tree", handleProjectTree).on("project.git_status", handleProjectGitStatus).on("agent.shutdown", async () => {
583
+ console.log(chalk2.yellow("\n \u26A1 Remote shutdown requested"));
269
584
  await shutdown();
270
585
  });
271
586
  await relay.start();
272
- console.log(chalk.green(" \u2713"), "Agent running. Press Ctrl+C to stop.\n");
587
+ console.log(chalk2.green(" \u2713"), "Agent running. Press Ctrl+C to stop.\n");
273
588
  process.on("SIGINT", shutdown);
274
589
  process.on("SIGTERM", shutdown);
275
590
  });
276
591
  program.command("status").description("Show agent configuration and status").action(() => {
277
592
  if (!configExists()) {
278
- console.log(chalk.red("\u2717"), "Not configured. Run `tenux login` first.");
593
+ console.log(chalk2.red("\u2717"), "Not configured. Run `tenux login` first.");
279
594
  process.exit(1);
280
595
  }
281
596
  const config = loadConfig();
282
597
  const claude = detectClaudeCode();
283
- console.log(chalk.bold("\n tenux"), chalk.dim("agent status\n"));
284
- console.log(chalk.dim(" Config:"), getConfigPath());
285
- console.log(chalk.dim(" App URL:"), config.appUrl);
286
- console.log(chalk.dim(" Device:"), config.deviceName);
287
- console.log(chalk.dim(" Device ID:"), config.deviceId);
288
- console.log(chalk.dim(" User ID:"), config.userId);
289
- console.log(chalk.dim(" Supabase:"), config.supabaseUrl);
290
- console.log(chalk.dim(" Projects:"), config.projectsDir);
598
+ console.log(chalk2.bold("\n tenux"), chalk2.dim("agent status\n"));
599
+ console.log(chalk2.dim(" Config:"), getConfigPath());
600
+ console.log(chalk2.dim(" App URL:"), config.appUrl);
601
+ console.log(chalk2.dim(" Device:"), config.deviceName);
602
+ console.log(chalk2.dim(" Device ID:"), config.deviceId);
603
+ console.log(chalk2.dim(" User ID:"), config.userId);
604
+ console.log(chalk2.dim(" Supabase:"), config.supabaseUrl);
605
+ console.log(chalk2.dim(" Projects:"), config.projectsDir);
291
606
  console.log(
292
- chalk.dim(" Auth:"),
293
- config.accessToken ? chalk.green("authenticated") : chalk.yellow("not authenticated")
607
+ chalk2.dim(" Auth:"),
608
+ config.accessToken ? chalk2.green("authenticated") : chalk2.yellow("not authenticated")
294
609
  );
295
610
  console.log(
296
- chalk.dim(" Claude Code:"),
297
- claude.installed ? chalk.green("installed") : chalk.yellow("not installed")
611
+ chalk2.dim(" Claude Code:"),
612
+ claude.installed ? chalk2.green("installed") : chalk2.yellow("not installed")
298
613
  );
299
614
  if (claude.installed) {
300
615
  console.log(
301
- chalk.dim(" Claude Auth:"),
302
- claude.authenticated ? chalk.green("yes (~/.claude exists)") : chalk.yellow("not authenticated")
616
+ chalk2.dim(" Claude Auth:"),
617
+ claude.authenticated ? chalk2.green("yes (~/.claude exists)") : chalk2.yellow("not authenticated")
303
618
  );
304
619
  }
305
620
  console.log();
@@ -307,7 +622,7 @@ program.command("status").description("Show agent configuration and status").act
307
622
  var configCmd = program.command("config").description("Manage agent configuration");
308
623
  configCmd.command("set <key> <value>").description("Set a config value").action((key, value) => {
309
624
  if (!configExists()) {
310
- console.log(chalk.red("\u2717"), "Not configured. Run `tenux login` first.");
625
+ console.log(chalk2.red("\u2717"), "Not configured. Run `tenux login` first.");
311
626
  process.exit(1);
312
627
  }
313
628
  const keyMap = {
@@ -318,17 +633,17 @@ configCmd.command("set <key> <value>").description("Set a config value").action(
318
633
  const configKey = keyMap[key];
319
634
  if (!configKey) {
320
635
  console.log(
321
- chalk.red("\u2717"),
636
+ chalk2.red("\u2717"),
322
637
  `Unknown config key: ${key}. Valid keys: ${Object.keys(keyMap).join(", ")}`
323
638
  );
324
639
  process.exit(1);
325
640
  }
326
641
  updateConfig({ [configKey]: value });
327
- console.log(chalk.green("\u2713"), `Set ${key}`);
642
+ console.log(chalk2.green("\u2713"), `Set ${key}`);
328
643
  });
329
644
  configCmd.command("get <key>").description("Get a config value").action((key) => {
330
645
  if (!configExists()) {
331
- console.log(chalk.red("\u2717"), "Not configured. Run `tenux login` first.");
646
+ console.log(chalk2.red("\u2717"), "Not configured. Run `tenux login` first.");
332
647
  process.exit(1);
333
648
  }
334
649
  const config = loadConfig();
@@ -343,12 +658,12 @@ configCmd.command("get <key>").description("Get a config value").action((key) =>
343
658
  const configKey = keyMap[key];
344
659
  if (!configKey) {
345
660
  console.log(
346
- chalk.red("\u2717"),
661
+ chalk2.red("\u2717"),
347
662
  `Unknown key: ${key}. Valid keys: ${Object.keys(keyMap).join(", ")}`
348
663
  );
349
664
  process.exit(1);
350
665
  }
351
666
  const val = config[configKey];
352
- console.log(val ?? chalk.dim("(not set)"));
667
+ console.log(val ?? chalk2.dim("(not set)"));
353
668
  });
354
669
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenux/cli",
3
- "version": "0.0.17",
3
+ "version": "0.0.18",
4
4
  "description": "Tenux — mobile-first IDE for 10x engineering",
5
5
  "author": "Antelogic LLC",
6
6
  "license": "MIT",