junis 0.1.5 → 0.1.8

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/dist/cli/index.js CHANGED
@@ -31,7 +31,7 @@ var require_package = __commonJS({
31
31
  "package.json"(exports2, module2) {
32
32
  module2.exports = {
33
33
  name: "junis",
34
- version: "0.1.5",
34
+ version: "0.1.8",
35
35
  description: "One-line device control for AI agents",
36
36
  bin: {
37
37
  junis: "dist/cli/index.js"
@@ -119,7 +119,8 @@ async function authenticate(deviceName, platform2, onBrowserOpen, onWaiting) {
119
119
  body: JSON.stringify({ device_name: deviceName, platform: platform2 })
120
120
  });
121
121
  if (!startRes.ok) {
122
- throw new Error(`Auth \uC2DC\uC791 \uC2E4\uD328: ${startRes.status}`);
122
+ const body = await startRes.text().catch(() => "");
123
+ throw new Error(`Auth \uC2DC\uC791 \uC2E4\uD328: ${startRes.status} ${body}`);
123
124
  }
124
125
  const startData = await startRes.json();
125
126
  const verificationUri = JUNIS_WEB ? startData.verification_uri.replace(/^https?:\/\/[^/]+/, JUNIS_WEB) : startData.verification_uri;
@@ -673,6 +674,7 @@ function platform() {
673
674
  return "linux";
674
675
  }
675
676
  var DeviceTools = class {
677
+ screenRecordPid = null;
676
678
  register(server) {
677
679
  server.tool(
678
680
  "screen_capture",
@@ -762,6 +764,80 @@ var DeviceTools = class {
762
764
  return { content: [{ type: "text", text: "\uD074\uB9BD\uBCF4\uB4DC \uC800\uC7A5 \uC644\uB8CC" }] };
763
765
  }
764
766
  );
767
+ server.tool(
768
+ "screen_record",
769
+ "\uD654\uBA74 \uB179\uD654 \uC2DC\uC791/\uC911\uC9C0 (macOS: screencapture -v, \uAE30\uD0C0: ffmpeg)",
770
+ {
771
+ action: import_zod4.z.enum(["start", "stop"]).describe("start: \uB179\uD654 \uC2DC\uC791, stop: \uB179\uD654 \uC911\uC9C0"),
772
+ output_path: import_zod4.z.string().optional().describe("\uC800\uC7A5 \uACBD\uB85C (start \uC2DC \uC0AC\uC6A9, \uAE30\uBCF8: /tmp/junis_record_<timestamp>.mp4)")
773
+ },
774
+ async ({ action, output_path }) => {
775
+ const p = platform();
776
+ if (action === "start") {
777
+ if (this.screenRecordPid) {
778
+ return { content: [{ type: "text", text: "\uC774\uBBF8 \uB179\uD654 \uC911\uC785\uB2C8\uB2E4." }] };
779
+ }
780
+ const tmpPath = output_path ?? `/tmp/junis_record_${Date.now()}.mp4`;
781
+ const { spawn } = await import("child_process");
782
+ const cmd = p === "mac" ? ["screencapture", ["-v", tmpPath]] : ["ffmpeg", ["-f", p === "win" ? "gdigrab" : "x11grab", "-i", p === "win" ? "desktop" : ":0.0", tmpPath]];
783
+ const child = spawn(cmd[0], cmd[1], { detached: true, stdio: "ignore" });
784
+ child.unref();
785
+ this.screenRecordPid = child.pid ?? null;
786
+ return { content: [{ type: "text", text: `\uB179\uD654 \uC2DC\uC791\uB428. \uC800\uC7A5 \uACBD\uB85C: ${tmpPath} (PID: ${this.screenRecordPid})` }] };
787
+ } else {
788
+ if (!this.screenRecordPid) {
789
+ return { content: [{ type: "text", text: "\uD604\uC7AC \uB179\uD654 \uC911\uC774 \uC544\uB2D9\uB2C8\uB2E4." }] };
790
+ }
791
+ try {
792
+ process.kill(this.screenRecordPid, "SIGINT");
793
+ } catch {
794
+ }
795
+ this.screenRecordPid = null;
796
+ return { content: [{ type: "text", text: "\uB179\uD654 \uC911\uC9C0\uB428." }] };
797
+ }
798
+ }
799
+ );
800
+ server.tool(
801
+ "location_get",
802
+ "\uD604\uC7AC \uC704\uCE58 \uC870\uD68C (macOS: CoreLocation CLI, \uAE30\uD0C0: IP \uAE30\uBC18 fallback)",
803
+ {},
804
+ async () => {
805
+ const p = platform();
806
+ if (p === "mac") {
807
+ try {
808
+ const { stdout } = await execAsync3("CoreLocationCLI -once -format '%latitude,%longitude'", { timeout: 1e4 });
809
+ const [lat, lon] = stdout.trim().split(",");
810
+ return { content: [{ type: "text", text: `\uC704\uB3C4: ${lat}, \uACBD\uB3C4: ${lon}` }] };
811
+ } catch {
812
+ }
813
+ }
814
+ const res = await fetch("https://ipapi.co/json/");
815
+ const data = await res.json();
816
+ return {
817
+ content: [{
818
+ type: "text",
819
+ text: `\uC704\uB3C4: ${data.latitude}, \uACBD\uB3C4: ${data.longitude}, \uB3C4\uC2DC: ${data.city}, \uAD6D\uAC00: ${data.country_name} (IP \uAE30\uBC18 \uCD94\uC815)`
820
+ }]
821
+ };
822
+ }
823
+ );
824
+ server.tool(
825
+ "audio_play",
826
+ "\uC624\uB514\uC624 \uD30C\uC77C \uC7AC\uC0DD (macOS: afplay, \uAE30\uD0C0: ffplay)",
827
+ {
828
+ file_path: import_zod4.z.string().describe("\uC7AC\uC0DD\uD560 \uC624\uB514\uC624 \uD30C\uC77C \uACBD\uB85C")
829
+ },
830
+ async ({ file_path }) => {
831
+ const p = platform();
832
+ const cmd = {
833
+ mac: `afplay "${file_path}"`,
834
+ win: `ffplay -nodisp -autoexit "${file_path}"`,
835
+ linux: `ffplay -nodisp -autoexit "${file_path}"`
836
+ }[p];
837
+ await execAsync3(cmd);
838
+ return { content: [{ type: "text", text: `\uC7AC\uC0DD \uC644\uB8CC: ${file_path}` }] };
839
+ }
840
+ );
765
841
  }
766
842
  };
767
843
 
@@ -1040,9 +1116,11 @@ function getDeviceName() {
1040
1116
  function printBanner() {
1041
1117
  console.log("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
1042
1118
  console.log("\u2551 \u2551");
1043
- console.log(`\u2551 \u{1F99E} J U N I S v${version} \u2551`);
1119
+ const versionLabel = `\u{1F99E} J U N I S v${version}`;
1120
+ const padding = " ".repeat(46 - 3 - versionLabel.length);
1121
+ console.log(`\u2551 ${versionLabel}${padding}\u2551`);
1044
1122
  console.log("\u2551 \u2551");
1045
- console.log("\u2551 Device Agent for AI Teams \u2551");
1123
+ console.log("\u2551 Device Agent for AI Teams \u2551");
1046
1124
  console.log("\u2551 \u2551");
1047
1125
  console.log("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
1048
1126
  console.log("");
@@ -1118,13 +1196,14 @@ import_commander.program.command("start", { isDefault: true }).description("Juni
1118
1196
  console.log(" \u25C9 Status ....................... \u{1F7E2} online");
1119
1197
  console.log("");
1120
1198
  if (authResult.agent_id) {
1199
+ const workspaceName = `${deviceName} Workspace`;
1121
1200
  console.log("\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\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\u2500\u2500\u2500");
1122
1201
  console.log(" STEP 4 \xB7 Create AI Team");
1123
1202
  console.log("\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\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\u2500\u2500\u2500");
1124
- console.log(" No existing team found. Creating your first AI team...");
1203
+ console.log(` \u25C9 Workspace created ............ ${workspaceName}`);
1125
1204
  console.log(` \u25C9 Agent created ................ ${authResult.agent_name}`);
1126
1205
  console.log(` \u25C9 Device linked ................ ${deviceName} \u2192 ${authResult.agent_name}`);
1127
- console.log(" \u25C9 Tools assigned ............... file_system, shell, browser, notebook, device");
1206
+ console.log(" \u25C9 Tools assigned ............... call_device_mcp, list_device_mcp_tools");
1128
1207
  console.log("");
1129
1208
  }
1130
1209
  } else {
@@ -1173,7 +1252,7 @@ import_commander.program.command("start", { isDefault: true }).description("Juni
1173
1252
  console.log(` \u2192 ${webUrl}`);
1174
1253
  console.log("\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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
1175
1254
  console.log("\u2502 \u2502");
1176
- console.log("\u2502 Try asking your AI: \u2502");
1255
+ console.log("\u2502 Try asking your AI: \u2502");
1177
1256
  console.log("\u2502 \u2502");
1178
1257
  console.log('\u2502 "\uB370\uC2A4\uD06C\uD1B1 \uD30C\uC77C \uBAA9\uB85D \uBCF4\uC5EC\uC918" \u2502');
1179
1258
  console.log('\u2502 "\uD06C\uB86C\uC5D0\uC11C \uC624\uB298 \uB274\uC2A4 \uAC80\uC0C9\uD574\uC918" \u2502');
@@ -446,6 +446,7 @@ function platform() {
446
446
  return "linux";
447
447
  }
448
448
  var DeviceTools = class {
449
+ screenRecordPid = null;
449
450
  register(server) {
450
451
  server.tool(
451
452
  "screen_capture",
@@ -535,6 +536,80 @@ var DeviceTools = class {
535
536
  return { content: [{ type: "text", text: "\uD074\uB9BD\uBCF4\uB4DC \uC800\uC7A5 \uC644\uB8CC" }] };
536
537
  }
537
538
  );
539
+ server.tool(
540
+ "screen_record",
541
+ "\uD654\uBA74 \uB179\uD654 \uC2DC\uC791/\uC911\uC9C0 (macOS: screencapture -v, \uAE30\uD0C0: ffmpeg)",
542
+ {
543
+ action: import_zod4.z.enum(["start", "stop"]).describe("start: \uB179\uD654 \uC2DC\uC791, stop: \uB179\uD654 \uC911\uC9C0"),
544
+ output_path: import_zod4.z.string().optional().describe("\uC800\uC7A5 \uACBD\uB85C (start \uC2DC \uC0AC\uC6A9, \uAE30\uBCF8: /tmp/junis_record_<timestamp>.mp4)")
545
+ },
546
+ async ({ action, output_path }) => {
547
+ const p = platform();
548
+ if (action === "start") {
549
+ if (this.screenRecordPid) {
550
+ return { content: [{ type: "text", text: "\uC774\uBBF8 \uB179\uD654 \uC911\uC785\uB2C8\uB2E4." }] };
551
+ }
552
+ const tmpPath = output_path ?? `/tmp/junis_record_${Date.now()}.mp4`;
553
+ const { spawn } = await import("child_process");
554
+ const cmd = p === "mac" ? ["screencapture", ["-v", tmpPath]] : ["ffmpeg", ["-f", p === "win" ? "gdigrab" : "x11grab", "-i", p === "win" ? "desktop" : ":0.0", tmpPath]];
555
+ const child = spawn(cmd[0], cmd[1], { detached: true, stdio: "ignore" });
556
+ child.unref();
557
+ this.screenRecordPid = child.pid ?? null;
558
+ return { content: [{ type: "text", text: `\uB179\uD654 \uC2DC\uC791\uB428. \uC800\uC7A5 \uACBD\uB85C: ${tmpPath} (PID: ${this.screenRecordPid})` }] };
559
+ } else {
560
+ if (!this.screenRecordPid) {
561
+ return { content: [{ type: "text", text: "\uD604\uC7AC \uB179\uD654 \uC911\uC774 \uC544\uB2D9\uB2C8\uB2E4." }] };
562
+ }
563
+ try {
564
+ process.kill(this.screenRecordPid, "SIGINT");
565
+ } catch {
566
+ }
567
+ this.screenRecordPid = null;
568
+ return { content: [{ type: "text", text: "\uB179\uD654 \uC911\uC9C0\uB428." }] };
569
+ }
570
+ }
571
+ );
572
+ server.tool(
573
+ "location_get",
574
+ "\uD604\uC7AC \uC704\uCE58 \uC870\uD68C (macOS: CoreLocation CLI, \uAE30\uD0C0: IP \uAE30\uBC18 fallback)",
575
+ {},
576
+ async () => {
577
+ const p = platform();
578
+ if (p === "mac") {
579
+ try {
580
+ const { stdout } = await execAsync3("CoreLocationCLI -once -format '%latitude,%longitude'", { timeout: 1e4 });
581
+ const [lat, lon] = stdout.trim().split(",");
582
+ return { content: [{ type: "text", text: `\uC704\uB3C4: ${lat}, \uACBD\uB3C4: ${lon}` }] };
583
+ } catch {
584
+ }
585
+ }
586
+ const res = await fetch("https://ipapi.co/json/");
587
+ const data = await res.json();
588
+ return {
589
+ content: [{
590
+ type: "text",
591
+ text: `\uC704\uB3C4: ${data.latitude}, \uACBD\uB3C4: ${data.longitude}, \uB3C4\uC2DC: ${data.city}, \uAD6D\uAC00: ${data.country_name} (IP \uAE30\uBC18 \uCD94\uC815)`
592
+ }]
593
+ };
594
+ }
595
+ );
596
+ server.tool(
597
+ "audio_play",
598
+ "\uC624\uB514\uC624 \uD30C\uC77C \uC7AC\uC0DD (macOS: afplay, \uAE30\uD0C0: ffplay)",
599
+ {
600
+ file_path: import_zod4.z.string().describe("\uC7AC\uC0DD\uD560 \uC624\uB514\uC624 \uD30C\uC77C \uACBD\uB85C")
601
+ },
602
+ async ({ file_path }) => {
603
+ const p = platform();
604
+ const cmd = {
605
+ mac: `afplay "${file_path}"`,
606
+ win: `ffplay -nodisp -autoexit "${file_path}"`,
607
+ linux: `ffplay -nodisp -autoexit "${file_path}"`
608
+ }[p];
609
+ await execAsync3(cmd);
610
+ return { content: [{ type: "text", text: `\uC7AC\uC0DD \uC644\uB8CC: ${file_path}` }] };
611
+ }
612
+ );
538
613
  }
539
614
  };
540
615
 
@@ -435,6 +435,7 @@ function platform() {
435
435
  return "linux";
436
436
  }
437
437
  var DeviceTools = class {
438
+ screenRecordPid = null;
438
439
  register(server) {
439
440
  server.tool(
440
441
  "screen_capture",
@@ -524,6 +525,80 @@ var DeviceTools = class {
524
525
  return { content: [{ type: "text", text: "\uD074\uB9BD\uBCF4\uB4DC \uC800\uC7A5 \uC644\uB8CC" }] };
525
526
  }
526
527
  );
528
+ server.tool(
529
+ "screen_record",
530
+ "\uD654\uBA74 \uB179\uD654 \uC2DC\uC791/\uC911\uC9C0 (macOS: screencapture -v, \uAE30\uD0C0: ffmpeg)",
531
+ {
532
+ action: import_zod4.z.enum(["start", "stop"]).describe("start: \uB179\uD654 \uC2DC\uC791, stop: \uB179\uD654 \uC911\uC9C0"),
533
+ output_path: import_zod4.z.string().optional().describe("\uC800\uC7A5 \uACBD\uB85C (start \uC2DC \uC0AC\uC6A9, \uAE30\uBCF8: /tmp/junis_record_<timestamp>.mp4)")
534
+ },
535
+ async ({ action, output_path }) => {
536
+ const p = platform();
537
+ if (action === "start") {
538
+ if (this.screenRecordPid) {
539
+ return { content: [{ type: "text", text: "\uC774\uBBF8 \uB179\uD654 \uC911\uC785\uB2C8\uB2E4." }] };
540
+ }
541
+ const tmpPath = output_path ?? `/tmp/junis_record_${Date.now()}.mp4`;
542
+ const { spawn } = await import("child_process");
543
+ const cmd = p === "mac" ? ["screencapture", ["-v", tmpPath]] : ["ffmpeg", ["-f", p === "win" ? "gdigrab" : "x11grab", "-i", p === "win" ? "desktop" : ":0.0", tmpPath]];
544
+ const child = spawn(cmd[0], cmd[1], { detached: true, stdio: "ignore" });
545
+ child.unref();
546
+ this.screenRecordPid = child.pid ?? null;
547
+ return { content: [{ type: "text", text: `\uB179\uD654 \uC2DC\uC791\uB428. \uC800\uC7A5 \uACBD\uB85C: ${tmpPath} (PID: ${this.screenRecordPid})` }] };
548
+ } else {
549
+ if (!this.screenRecordPid) {
550
+ return { content: [{ type: "text", text: "\uD604\uC7AC \uB179\uD654 \uC911\uC774 \uC544\uB2D9\uB2C8\uB2E4." }] };
551
+ }
552
+ try {
553
+ process.kill(this.screenRecordPid, "SIGINT");
554
+ } catch {
555
+ }
556
+ this.screenRecordPid = null;
557
+ return { content: [{ type: "text", text: "\uB179\uD654 \uC911\uC9C0\uB428." }] };
558
+ }
559
+ }
560
+ );
561
+ server.tool(
562
+ "location_get",
563
+ "\uD604\uC7AC \uC704\uCE58 \uC870\uD68C (macOS: CoreLocation CLI, \uAE30\uD0C0: IP \uAE30\uBC18 fallback)",
564
+ {},
565
+ async () => {
566
+ const p = platform();
567
+ if (p === "mac") {
568
+ try {
569
+ const { stdout } = await execAsync3("CoreLocationCLI -once -format '%latitude,%longitude'", { timeout: 1e4 });
570
+ const [lat, lon] = stdout.trim().split(",");
571
+ return { content: [{ type: "text", text: `\uC704\uB3C4: ${lat}, \uACBD\uB3C4: ${lon}` }] };
572
+ } catch {
573
+ }
574
+ }
575
+ const res = await fetch("https://ipapi.co/json/");
576
+ const data = await res.json();
577
+ return {
578
+ content: [{
579
+ type: "text",
580
+ text: `\uC704\uB3C4: ${data.latitude}, \uACBD\uB3C4: ${data.longitude}, \uB3C4\uC2DC: ${data.city}, \uAD6D\uAC00: ${data.country_name} (IP \uAE30\uBC18 \uCD94\uC815)`
581
+ }]
582
+ };
583
+ }
584
+ );
585
+ server.tool(
586
+ "audio_play",
587
+ "\uC624\uB514\uC624 \uD30C\uC77C \uC7AC\uC0DD (macOS: afplay, \uAE30\uD0C0: ffplay)",
588
+ {
589
+ file_path: import_zod4.z.string().describe("\uC7AC\uC0DD\uD560 \uC624\uB514\uC624 \uD30C\uC77C \uACBD\uB85C")
590
+ },
591
+ async ({ file_path }) => {
592
+ const p = platform();
593
+ const cmd = {
594
+ mac: `afplay "${file_path}"`,
595
+ win: `ffplay -nodisp -autoexit "${file_path}"`,
596
+ linux: `ffplay -nodisp -autoexit "${file_path}"`
597
+ }[p];
598
+ await execAsync3(cmd);
599
+ return { content: [{ type: "text", text: `\uC7AC\uC0DD \uC644\uB8CC: ${file_path}` }] };
600
+ }
601
+ );
527
602
  }
528
603
  };
529
604
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "junis",
3
- "version": "0.1.5",
3
+ "version": "0.1.8",
4
4
  "description": "One-line device control for AI agents",
5
5
  "bin": {
6
6
  "junis": "dist/cli/index.js"