@notjustyou/cli 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Not Just You
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # @notjustyou/cli
2
+
3
+ Read-only command line status lookup for Not Just You AI service surfaces.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @notjustyou/cli
9
+ ```
10
+
11
+ You can also run it from a workspace checkout:
12
+
13
+ ```bash
14
+ pnpm --filter @notjustyou/cli build
15
+ node packages/notjustyou-cli/dist/index.js status --base-url http://localhost:3000
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ```bash
21
+ njy status
22
+ njy status openai-api
23
+ njy status openai-api --watch
24
+ njy status --base-url http://localhost:3000
25
+ ```
26
+
27
+ The CLI reads public status summaries only:
28
+
29
+ - `/api/summary`
30
+ - `/api/signals/summary`
31
+ - `/api/official`
32
+
33
+ It does not submit reports or installed-client signals, and it does not require collector credentials.
34
+
35
+ ## Privacy Boundary
36
+
37
+ The CLI does not collect prompt text, request or response bodies, headers, API keys, cookies, source files, diffs, clipboard content, exact IP addresses, account emails, or machine/user names.
package/dist/api.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { StatusData } from "./types.js";
2
+ export declare function fetchStatusData(baseUrl: string): Promise<StatusData>;
3
+ export declare function normalizeBaseUrl(baseUrl: string): string;
package/dist/api.js ADDED
@@ -0,0 +1,30 @@
1
+ export async function fetchStatusData(baseUrl) {
2
+ const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
3
+ const [communityResult, installedSignalsResult, officialResult] = await Promise.allSettled([
4
+ fetchJson(`${normalizedBaseUrl}/api/summary`),
5
+ fetchJson(`${normalizedBaseUrl}/api/signals/summary`),
6
+ fetchJson(`${normalizedBaseUrl}/api/official`),
7
+ ]);
8
+ if (communityResult.status === "rejected") {
9
+ throw communityResult.reason;
10
+ }
11
+ return {
12
+ community: communityResult.value,
13
+ installedSignals: installedSignalsResult.status === "fulfilled"
14
+ ? installedSignalsResult.value
15
+ : null,
16
+ official: officialResult.status === "fulfilled" ? officialResult.value : null,
17
+ };
18
+ }
19
+ export function normalizeBaseUrl(baseUrl) {
20
+ return baseUrl.replace(/\/+$/, "");
21
+ }
22
+ async function fetchJson(url) {
23
+ const response = await fetch(url, {
24
+ cache: "no-store",
25
+ });
26
+ if (!response.ok) {
27
+ throw new Error(`Request failed: ${url} (${response.status})`);
28
+ }
29
+ return (await response.json());
30
+ }
@@ -0,0 +1,2 @@
1
+ import type { StatusData } from "./types.js";
2
+ export declare function formatStatus(data: StatusData, serviceId?: string): string;
package/dist/format.js ADDED
@@ -0,0 +1,49 @@
1
+ export function formatStatus(data, serviceId) {
2
+ const serviceIds = getServiceIds(data, serviceId);
3
+ if (serviceId && serviceIds.length === 0) {
4
+ return `No status found for ${serviceId}.`;
5
+ }
6
+ return serviceIds
7
+ .map((id) => formatServiceStatus({
8
+ serviceId: id,
9
+ community: data.community.services.find((service) => service.serviceId === id),
10
+ installedSignals: data.installedSignals?.services.find((service) => service.serviceId === id),
11
+ official: data.official?.services.find((service) => service.serviceId === id),
12
+ installedSignalsAvailable: Boolean(data.installedSignals),
13
+ }))
14
+ .join("\n\n");
15
+ }
16
+ function getServiceIds(data, serviceId) {
17
+ if (serviceId) {
18
+ const exists = data.community.services.some((service) => service.serviceId === serviceId) ||
19
+ data.installedSignals?.services.some((service) => service.serviceId === serviceId) ||
20
+ Boolean(data.official?.services.some((service) => service.serviceId === serviceId));
21
+ return exists ? [serviceId] : [];
22
+ }
23
+ return data.community.services.map((service) => service.serviceId);
24
+ }
25
+ function formatServiceStatus(input) {
26
+ const communityTotal = input.community?.total ?? 0;
27
+ const installedTotal = input.installedSignals?.total ?? 0;
28
+ const officialText = input.official
29
+ ? `${formatValue(input.official.overall)} (${input.official.source})`
30
+ : "unavailable";
31
+ const lines = [
32
+ input.serviceId,
33
+ `Community reports: ${communityTotal}`,
34
+ input.installedSignalsAvailable
35
+ ? `Installed signals: ${installedTotal}`
36
+ : "Installed signals: unavailable",
37
+ input.installedSignalsAvailable
38
+ ? `Unique installations: ${input.installedSignals?.uniqueInstallationsApprox ?? 0}`
39
+ : "Unique installations: unavailable",
40
+ `Official: ${officialText}`,
41
+ ];
42
+ if (input.installedSignals?.lastSignal) {
43
+ lines.push(`Last installed signal: ${formatValue(input.installedSignals.lastSignal.symptom)}`);
44
+ }
45
+ return lines.join("\n");
46
+ }
47
+ function formatValue(value) {
48
+ return value.replaceAll("_", " ");
49
+ }
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ export declare function main(argv?: string[]): Promise<0 | 1>;
3
+ export declare function parseCliArgs(argv: string[]): {
4
+ command: string;
5
+ serviceId: string;
6
+ watch: boolean;
7
+ baseUrl: string;
8
+ };
9
+ export declare function isDirectRun(metaUrl: string, argvPath?: string): boolean;
package/dist/index.js ADDED
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+ import { realpathSync } from "node:fs";
3
+ import { parseArgs } from "node:util";
4
+ import { setTimeout as delay } from "node:timers/promises";
5
+ import { fileURLToPath } from "node:url";
6
+ import { fetchStatusData } from "./api.js";
7
+ import { formatStatus } from "./format.js";
8
+ const DEFAULT_BASE_URL = "https://notjustyou.dev";
9
+ const WATCH_INTERVAL_MS = 2_000;
10
+ export async function main(argv = process.argv.slice(2)) {
11
+ const parsed = parseCliArgs(argv);
12
+ if (parsed.command !== "status") {
13
+ printUsage();
14
+ return parsed.command === "help" ? 0 : 1;
15
+ }
16
+ await runStatus({
17
+ baseUrl: parsed.baseUrl,
18
+ serviceId: parsed.serviceId,
19
+ watch: parsed.watch,
20
+ });
21
+ return 0;
22
+ }
23
+ export function parseCliArgs(argv) {
24
+ const { values, positionals } = parseArgs({
25
+ args: argv,
26
+ allowPositionals: true,
27
+ options: {
28
+ "base-url": {
29
+ type: "string",
30
+ },
31
+ watch: {
32
+ type: "boolean",
33
+ default: false,
34
+ },
35
+ help: {
36
+ type: "boolean",
37
+ short: "h",
38
+ default: false,
39
+ },
40
+ },
41
+ });
42
+ const [command = "", serviceId] = positionals;
43
+ return {
44
+ command: values.help ? "help" : command,
45
+ serviceId,
46
+ watch: values.watch ?? false,
47
+ baseUrl: values["base-url"] ?? process.env.NOTJUSTYOU_BASE_URL ?? DEFAULT_BASE_URL,
48
+ };
49
+ }
50
+ async function runStatus(input) {
51
+ let keepRunning = true;
52
+ process.once("SIGINT", () => {
53
+ keepRunning = false;
54
+ });
55
+ do {
56
+ const data = await fetchStatusData(input.baseUrl);
57
+ const output = formatStatus(data, input.serviceId);
58
+ console.log(output);
59
+ if (!input.watch)
60
+ return;
61
+ await delay(WATCH_INTERVAL_MS);
62
+ if (keepRunning) {
63
+ console.log("");
64
+ }
65
+ } while (keepRunning);
66
+ }
67
+ function printUsage() {
68
+ console.log(`Usage:
69
+ njy status [serviceId] [--base-url <url>] [--watch]
70
+
71
+ Examples:
72
+ njy status
73
+ njy status openai-api --base-url http://localhost:3000
74
+ njy status openai-api --watch`);
75
+ }
76
+ export function isDirectRun(metaUrl, argvPath = process.argv[1]) {
77
+ if (!argvPath)
78
+ return false;
79
+ try {
80
+ return realpathSync(argvPath) === realpathSync(fileURLToPath(metaUrl));
81
+ }
82
+ catch {
83
+ return false;
84
+ }
85
+ }
86
+ if (isDirectRun(import.meta.url)) {
87
+ main().then((exitCode) => {
88
+ process.exitCode = exitCode;
89
+ }, (error) => {
90
+ console.error(error instanceof Error ? error.message : String(error));
91
+ process.exitCode = 1;
92
+ });
93
+ }
@@ -0,0 +1,47 @@
1
+ export interface CommunityServiceSummary {
2
+ serviceId: string;
3
+ total: number;
4
+ counts: {
5
+ slow: number;
6
+ error: number;
7
+ down: number;
8
+ };
9
+ communityState: string;
10
+ }
11
+ export interface CommunitySummaryResponse {
12
+ windowMinutes: number;
13
+ updatedAt: string;
14
+ services: CommunityServiceSummary[];
15
+ }
16
+ export interface InstalledSignalServiceSummary {
17
+ serviceId: string;
18
+ total: number;
19
+ uniqueInstallationsApprox: number;
20
+ countsBySource: Record<string, number>;
21
+ countsBySymptom: Record<string, number>;
22
+ lastSignal: {
23
+ symptom: string;
24
+ source: string;
25
+ observedAt: string;
26
+ } | null;
27
+ }
28
+ export interface InstalledSignalSummaryResponse {
29
+ windowMinutes: number;
30
+ updatedAt: string;
31
+ services: InstalledSignalServiceSummary[];
32
+ }
33
+ export interface OfficialServiceStatus {
34
+ serviceId: string;
35
+ overall: string;
36
+ source: "official" | "not_connected";
37
+ updatedAt: string;
38
+ }
39
+ export interface OfficialSummaryResponse {
40
+ updatedAt: string;
41
+ services: OfficialServiceStatus[];
42
+ }
43
+ export interface StatusData {
44
+ community: CommunitySummaryResponse;
45
+ installedSignals: InstalledSignalSummaryResponse | null;
46
+ official: OfficialSummaryResponse | null;
47
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@notjustyou/cli",
3
+ "version": "0.1.0",
4
+ "description": "Read-only command line status lookup for Not Just You AI service surfaces.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/dobbylee/notjustyou.git",
9
+ "directory": "packages/notjustyou-cli"
10
+ },
11
+ "homepage": "https://github.com/dobbylee/notjustyou#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/dobbylee/notjustyou/issues"
14
+ },
15
+ "keywords": [
16
+ "ai-status",
17
+ "status",
18
+ "cli",
19
+ "notjustyou"
20
+ ],
21
+ "type": "module",
22
+ "files": [
23
+ "dist",
24
+ "LICENSE",
25
+ "README.md"
26
+ ],
27
+ "main": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "import": "./dist/index.js"
33
+ }
34
+ },
35
+ "bin": {
36
+ "njy": "dist/index.js"
37
+ },
38
+ "scripts": {
39
+ "build": "tsc -p tsconfig.json",
40
+ "prepack": "pnpm run build",
41
+ "pack:dry-run": "pnpm pack --dry-run"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "engines": {
47
+ "node": ">=20.9.0"
48
+ }
49
+ }