@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.
- package/README.md +5 -2
- package/dist/index.js +124 -27
- 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 /
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
24581
|
-
|
|
24582
|
-
|
|
24583
|
-
|
|
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
|
-
|
|
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
|
-
|
|
24662
|
+
fatalExit(1);
|
|
24597
24663
|
}
|
|
24598
24664
|
if (!workspaces.length) {
|
|
24599
24665
|
console.error("Error: no workspaces found for this user");
|
|
24600
|
-
|
|
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
|
|
24683
|
-
let
|
|
24750
|
+
const result = await activateAndSave({ token, serverUrl, profile });
|
|
24751
|
+
let email3 = "";
|
|
24684
24752
|
try {
|
|
24685
|
-
|
|
24686
|
-
|
|
24687
|
-
|
|
24688
|
-
|
|
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
|
-
|
|
24973
|
-
lines.push(
|
|
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
|
|
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
|
|
26592
|
-
hint:
|
|
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
|
|
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) =>
|
|
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 {
|