@phneakngar/cli 0.0.1 → 0.0.2

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 (3) hide show
  1. package/README.md +5 -2
  2. package/dist/index.js +124 -27
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -19,11 +19,14 @@ phneakngar version
19
19
  npx @phneakngar/cli <command>
20
20
  ```
21
21
 
22
- ### From a local pack (pre-publish / offline)
22
+ ### From a local pack (pre-publish / operator distribution)
23
+
24
+ The tarball contains the CLI build, but npm still resolves its runtime dependencies from the configured registry unless they are already cached. It is not a fully offline bundle.
23
25
 
24
26
  ```bash
25
27
  # built by an operator from the monorepo
26
- npm install --global ./phneakngar-cli-0.0.149.tgz
28
+ npm install --global ./phneakngar-cli-X.Y.Z.tgz
29
+ phneakngar version
27
30
  ```
28
31
 
29
32
  ## Quick Start
package/dist/index.js CHANGED
@@ -24559,33 +24559,99 @@ function detectRuntimes() {
24559
24559
  }
24560
24560
 
24561
24561
  // lib/activate.ts
24562
+ var AL_TOKEN_RE = /al_[A-Za-z0-9_-]{8,}/g;
24563
+ var MAX_BODY_CHARS = 200;
24564
+ function sanitizeActivateBody(bodyText) {
24565
+ const redacted = bodyText.replace(AL_TOKEN_RE, "al_[redacted]");
24566
+ if (redacted.length <= MAX_BODY_CHARS)
24567
+ return redacted;
24568
+ return `${redacted.slice(0, MAX_BODY_CHARS)}…`;
24569
+ }
24570
+ function extractServerError(bodyText) {
24571
+ const trimmed = bodyText.trim();
24572
+ if (!trimmed)
24573
+ return "";
24574
+ try {
24575
+ const parsed = JSON.parse(trimmed);
24576
+ if (typeof parsed?.error === "string")
24577
+ return parsed.error;
24578
+ } catch {}
24579
+ return trimmed;
24580
+ }
24581
+ function formatActivateFailure(status, bodyText) {
24582
+ const shortBody = sanitizeActivateBody(bodyText || "(empty body)");
24583
+ const serverError = extractServerError(bodyText).toLowerCase();
24584
+ const hint = activateFailureHint(status, serverError);
24585
+ if (hint) {
24586
+ return `Error: registration failed (${status}): ${shortBody}
24587
+ → ${hint}`;
24588
+ }
24589
+ return `Error: registration failed (${status}): ${shortBody}`;
24590
+ }
24591
+ function activateFailureHint(status, serverError) {
24592
+ if (status === 404 || serverError.includes("token not found")) {
24593
+ return "Token not found on this server. Check the server URL " + `('${cmdPrefix()} init --server <url>' / config) and copy a fresh token from the UI.`;
24594
+ }
24595
+ if (status === 422 || serverError.includes("workspace_id") || serverError.includes("no workspace")) {
24596
+ return "Token has no workspace. Open or create a workspace in the app first, " + "then generate a new token.";
24597
+ }
24598
+ if (status === 409) {
24599
+ if (serverError.includes("another user")) {
24600
+ return "This machine hostname is already linked to another user. " + "Use a different hostname or create a new token in the UI after resolving ownership.";
24601
+ }
24602
+ if (serverError.includes("already claimed") || serverError.includes("another machine") || serverError.includes("already used") || serverError.includes("could not be finalized")) {
24603
+ return "Token already used on another machine (or a different runtime set). " + "Create a new token in the UI for this machine.";
24604
+ }
24605
+ return "Token activation conflict (already claimed or used). " + "Create a new token in the UI and try again.";
24606
+ }
24607
+ if (status === 503 || serverError.includes("temporarily unavailable")) {
24608
+ return "Token activation temporarily unavailable. Retry in a moment.";
24609
+ }
24610
+ if (status === 400) {
24611
+ return "Invalid activation request. Re-copy the token from the UI and ensure " + "at least one runtime (claude, codex, opencode, or grok) is installed.";
24612
+ }
24613
+ if (status === 401) {
24614
+ return "Server rejected this token. Copy a fresh token from the UI and confirm " + `the server URL ('${cmdPrefix()} init --server <url>' / config).`;
24615
+ }
24616
+ return null;
24617
+ }
24618
+ function fatalExit(code = 1) {
24619
+ process.exit(code);
24620
+ throw new Error(`process.exit(${code})`);
24621
+ }
24562
24622
  async function activateAndSave(opts) {
24563
24623
  const { token, serverUrl, profile } = opts;
24564
24624
  console.log("Scanning for AI runtimes...");
24565
24625
  const runtimes = detectRuntimes();
24566
24626
  if (runtimes.length === 0) {
24567
24627
  console.error("Error: no runtimes found. Install claude, codex, opencode, or grok first.");
24568
- process.exit(1);
24628
+ fatalExit(1);
24569
24629
  }
24570
24630
  console.log(`Found: ${runtimes.map((r) => r.type).join(", ")}`);
24571
24631
  const host = hostname4();
24572
24632
  console.log("Registering machine...");
24573
24633
  let activateResp;
24634
+ let res;
24574
24635
  try {
24575
- const res = await fetch(`${serverUrl}/api/machine-tokens/activate`, {
24636
+ res = await fetch(`${serverUrl}/api/machine-tokens/activate`, {
24576
24637
  method: "POST",
24577
24638
  headers: { "Content-Type": "application/json" },
24578
24639
  body: JSON.stringify({ token, hostname: host, runtimes })
24579
24640
  });
24580
- if (!res.ok) {
24581
- const text2 = await res.text();
24582
- console.error(`Error: registration failed (${res.status}): ${text2}`);
24583
- process.exit(1);
24584
- }
24641
+ } catch (err) {
24642
+ console.error(`Error: failed to activate: ${err instanceof Error ? err.message : err}`);
24643
+ fatalExit(1);
24644
+ }
24645
+ if (!res.ok) {
24646
+ const text2 = await res.text();
24647
+ console.error(formatActivateFailure(res.status, text2));
24648
+ fatalExit(1);
24649
+ }
24650
+ try {
24585
24651
  activateResp = await res.json();
24586
24652
  } catch (err) {
24587
24653
  console.error(`Error: failed to activate: ${err instanceof Error ? err.message : err}`);
24588
- process.exit(1);
24654
+ fatalExit(1);
24589
24655
  }
24590
24656
  const client = new APIClient(serverUrl, token);
24591
24657
  let workspaces;
@@ -24593,11 +24659,11 @@ async function activateAndSave(opts) {
24593
24659
  workspaces = await client.getJSON("/api/workspaces");
24594
24660
  } catch (err) {
24595
24661
  console.error(`Error: failed to fetch workspaces: ${err instanceof Error ? err.message : err}`);
24596
- process.exit(1);
24662
+ fatalExit(1);
24597
24663
  }
24598
24664
  if (!workspaces.length) {
24599
24665
  console.error("Error: no workspaces found for this user");
24600
- process.exit(1);
24666
+ fatalExit(1);
24601
24667
  }
24602
24668
  const ws = workspaces.find((w) => w.id === activateResp.workspace_id) || workspaces[0];
24603
24669
  const wsClient = new APIClient(serverUrl, token, ws.id);
@@ -24674,22 +24740,24 @@ function registerCommand() {
24674
24740
  console.error(`Error: --token is required
24675
24741
  Usage: ${cmdPrefix()} register --token <token>`);
24676
24742
  process.exit(1);
24743
+ return;
24677
24744
  }
24678
24745
  if (!token.startsWith("al_")) {
24679
24746
  console.error("Error: invalid token format: must start with 'al_'");
24680
24747
  process.exit(1);
24748
+ return;
24681
24749
  }
24682
- const client = new APIClient(serverUrl, token);
24683
- let me;
24750
+ const result = await activateAndSave({ token, serverUrl, profile });
24751
+ let email3 = "";
24684
24752
  try {
24685
- me = await client.getJSON("/api/me");
24686
- } catch (err) {
24687
- console.error(`Error: failed to verify token: ${err instanceof Error ? err.message : err}`);
24688
- process.exit(1);
24753
+ const client = new APIClient(serverUrl, token);
24754
+ const me = await client.getJSON("/api/me");
24755
+ email3 = me.email;
24756
+ } catch {}
24757
+ if (email3) {
24758
+ console.log(`
24759
+ Registered as ${email3}`);
24689
24760
  }
24690
- const result = await activateAndSave({ token, serverUrl, profile });
24691
- console.log(`
24692
- Registered as ${me.email}`);
24693
24761
  console.log(`Workspace: ${result.workspaceName} (${result.workspaceId})`);
24694
24762
  console.log(`Runtimes: ${result.runtimeProviders.join(", ")}`);
24695
24763
  });
@@ -24969,8 +25037,9 @@ function formatStatusReport(profile) {
24969
25037
  lines.push(`CLI version: ${getCurrentVersion()}`);
24970
25038
  lines.push(`Server: ${server}`);
24971
25039
  if (!ws?.token) {
24972
- lines.push("Registration: Not registered");
24973
- lines.push(`Hint: Run '${cmdPrefix()} login' or '${cmdPrefix()} register --token <token>'`);
25040
+ const empty = !cfg.watched_workspaces?.length || !cfg.watched_workspaces.some((w) => w.token && w.status !== "deleted");
25041
+ lines.push(empty && !cfg.watched_workspaces?.length ? "Registration: Not registered (watched_workspaces empty)" : "Registration: Not registered (no active machine token)");
25042
+ lines.push(`Hint: Run '${cmdPrefix()} register --token al_...' (per workspace). Or '${cmdPrefix()} login'.`);
24974
25043
  } else {
24975
25044
  lines.push("Registration: Registered");
24976
25045
  lines.push(`Workspace: ${ws.name || "unknown"} (${ws.id || "no-id"})`);
@@ -26530,6 +26599,7 @@ Workspace initialized: ${res.studio.name || res.workspace.name}`);
26530
26599
  // commands/doctor.ts
26531
26600
  import { Command as Command14 } from "commander";
26532
26601
  import { existsSync as existsSync5, accessSync, constants } from "fs";
26602
+ import { join as join15 } from "path";
26533
26603
  var MIN_NODE_MAJOR = 20;
26534
26604
  var MIN_NODE_MINOR = 19;
26535
26605
  function nodeVersionParts(version3 = process.versions.node) {
@@ -26583,19 +26653,45 @@ function checkConfig(profile) {
26583
26653
  }
26584
26654
  function checkRegistration(profile) {
26585
26655
  const cfg = loadCLIConfigForProfile(profile);
26586
- const ws = cfg.watched_workspaces?.find((w) => w.token && w.status !== "deleted");
26656
+ const list = cfg.watched_workspaces ?? [];
26657
+ const ws = list.find((w) => w.token && w.status !== "deleted");
26658
+ const registerHint = `Run '${cmdPrefix()} register --token al_...' (per workspace; create token in the dashboard). Or '${cmdPrefix()} login'.`;
26587
26659
  if (!ws?.token) {
26660
+ let detail;
26661
+ if (list.length === 0) {
26662
+ detail = "not registered — watched_workspaces empty (no machine token for this PC)";
26663
+ } else {
26664
+ const deleted = list.filter((w) => w.status === "deleted").length;
26665
+ const tokenless = list.filter((w) => !w.token).length;
26666
+ detail = `not registered — no active watched workspace token (${list.length} entries, none usable`;
26667
+ const parts = [];
26668
+ if (deleted)
26669
+ parts.push(`${deleted} deleted`);
26670
+ if (tokenless)
26671
+ parts.push(`${tokenless} without token`);
26672
+ if (parts.length)
26673
+ detail += `; ${parts.join(", ")}`;
26674
+ detail += ")";
26675
+ }
26588
26676
  return {
26589
26677
  name: "Registration",
26590
26678
  status: "fail",
26591
- detail: "not registered",
26592
- hint: `Run '${cmdPrefix()} login' or '${cmdPrefix()} register --token al_...'`
26679
+ detail,
26680
+ hint: registerHint
26593
26681
  };
26594
26682
  }
26595
26683
  return {
26596
26684
  name: "Registration",
26597
26685
  status: "pass",
26598
- detail: `workspace ${ws.name || "unknown"} (${ws.id || "no-id"})`
26686
+ detail: `workspace ${ws.name || "unknown"} (${ws.id || "no-id"}) — agent can use this PC once chhlat is running`
26687
+ };
26688
+ }
26689
+ function checkAgentWorkdirScope(profile) {
26690
+ const root = join15(configDir(), "workspaces");
26691
+ return {
26692
+ name: "Agent workdir scope",
26693
+ status: "info",
26694
+ detail: `agents only access agent workdirs under ${root} (not the whole PC)`
26599
26695
  };
26600
26696
  }
26601
26697
  function checkRuntimes() {
@@ -26712,6 +26808,7 @@ async function runDoctor(profile, options = {}) {
26712
26808
  checkCliVersion(),
26713
26809
  checkConfig(profile),
26714
26810
  checkRegistration(profile),
26811
+ checkAgentWorkdirScope(profile),
26715
26812
  checkRuntimes(),
26716
26813
  checkChhlat(profile)
26717
26814
  ];
@@ -26769,11 +26866,11 @@ function doctorCommand() {
26769
26866
  // commands/logs.ts
26770
26867
  import { Command as Command15 } from "commander";
26771
26868
  import { existsSync as existsSync6, readdirSync as readdirSync5, readFileSync as readFileSync16, statSync as statSync6 } from "fs";
26772
- import { join as join15 } from "path";
26869
+ import { join as join16 } from "path";
26773
26870
  function listLogFiles(dir = chhlatLogDir()) {
26774
26871
  if (!existsSync6(dir))
26775
26872
  return [];
26776
- return readdirSync5(dir).filter((name) => name.endsWith(".log")).map((name) => join15(dir, name)).sort((a, b) => {
26873
+ return readdirSync5(dir).filter((name) => name.endsWith(".log")).map((name) => join16(dir, name)).sort((a, b) => {
26777
26874
  try {
26778
26875
  return statSync6(b).mtimeMs - statSync6(a).mtimeMs;
26779
26876
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phneakngar/cli",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "ភ្នាក់ងារ CLI — install and run local AI agents on a client machine",
5
5
  "license": "Apache-2.0",
6
6
  "author": "ភ្នាក់ងារ",