@andocorp/cli 0.2.0 → 0.3.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 +21 -0
- package/README.md +83 -141
- package/dist/agent-commands.js +297 -0
- package/dist/api-command.js +187 -0
- package/dist/api-inputs.js +223 -0
- package/dist/api-operations.js +344 -0
- package/dist/args.js +71 -0
- package/dist/auth-commands.js +362 -0
- package/dist/cli-helpers.js +67 -0
- package/dist/cli-login-browser.js +60 -0
- package/dist/cli-login-errors.js +10 -0
- package/dist/cli-login-paths.js +8 -0
- package/dist/cli-login-revoke.js +100 -0
- package/dist/cli-login.js +335 -0
- package/dist/client.js +104 -0
- package/dist/commands.js +155 -0
- package/dist/config-credential-metadata.js +68 -0
- package/dist/config-keyring.js +61 -0
- package/dist/config-logout-credentials.js +171 -0
- package/dist/config-paths.js +41 -0
- package/dist/config-types.js +1 -0
- package/dist/config.js +333 -0
- package/dist/format.js +297 -0
- package/dist/help.js +70 -0
- package/dist/index.js +77 -34266
- package/dist/output.js +7 -0
- package/dist/session.js +58 -0
- package/dist/timeouts.js +1 -0
- package/dist/types.js +1 -0
- package/dist/watch-commands.js +120 -0
- package/package.json +15 -16
package/dist/output.js
ADDED
package/dist/session.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { DEFAULT_ANDO_PUBLIC_API_BASE_URL } from "@andocorp/sdk";
|
|
2
|
+
import { getStringFlag } from "./args.js";
|
|
3
|
+
import { readSavedConfig, readSavedConfigMetadata } from "./config.js";
|
|
4
|
+
const LOGIN_REQUIRED_ERROR = `API key is required. Run "ando login" or set ANDO_API_KEY.`;
|
|
5
|
+
function getOptionalStringFlag(parsedArgs, name) {
|
|
6
|
+
const value = getStringFlag(parsedArgs, name);
|
|
7
|
+
return value == null || value.trim() === "" ? null : value.trim();
|
|
8
|
+
}
|
|
9
|
+
function getOptionalStringEnv(name) {
|
|
10
|
+
const value = process.env[name];
|
|
11
|
+
return value == null || value.trim() === "" ? null : value.trim();
|
|
12
|
+
}
|
|
13
|
+
function getExplicitApiKey(parsedArgs) {
|
|
14
|
+
return (getOptionalStringFlag(parsedArgs, "api-key") ?? getOptionalStringEnv("ANDO_API_KEY"));
|
|
15
|
+
}
|
|
16
|
+
export function getConfiguredBaseUrl(parsedArgs, savedBaseUrl) {
|
|
17
|
+
return (getOptionalStringFlag(parsedArgs, "base-url") ??
|
|
18
|
+
getOptionalStringEnv("ANDO_BASE_URL") ??
|
|
19
|
+
savedBaseUrl ??
|
|
20
|
+
undefined);
|
|
21
|
+
}
|
|
22
|
+
export function getConfiguredRealtimeHost(parsedArgs, savedRealtimeHost) {
|
|
23
|
+
return (getOptionalStringFlag(parsedArgs, "realtime-host") ??
|
|
24
|
+
getOptionalStringEnv("ANDO_REALTIME_HOST") ??
|
|
25
|
+
savedRealtimeHost ??
|
|
26
|
+
undefined);
|
|
27
|
+
}
|
|
28
|
+
export function getConfiguredApiHost(parsedArgs, savedApiHost) {
|
|
29
|
+
return (getOptionalStringFlag(parsedArgs, "api-host") ??
|
|
30
|
+
getOptionalStringEnv("ANDO_API_HOST") ??
|
|
31
|
+
savedApiHost ??
|
|
32
|
+
DEFAULT_ANDO_PUBLIC_API_BASE_URL);
|
|
33
|
+
}
|
|
34
|
+
function buildConfig(parsedArgs, apiKey, savedConfig) {
|
|
35
|
+
if (apiKey == null) {
|
|
36
|
+
throw new Error(LOGIN_REQUIRED_ERROR);
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
apiKey,
|
|
40
|
+
baseUrl: getConfiguredBaseUrl(parsedArgs, savedConfig?.baseUrl ?? null),
|
|
41
|
+
apiHost: getConfiguredApiHost(parsedArgs, savedConfig?.apiHost ?? null),
|
|
42
|
+
realtimeHost: getConfiguredRealtimeHost(parsedArgs, savedConfig?.realtimeHost ?? null),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async function loadConfig(parsedArgs) {
|
|
46
|
+
const explicitApiKey = getExplicitApiKey(parsedArgs);
|
|
47
|
+
if (explicitApiKey != null) {
|
|
48
|
+
return buildConfig(parsedArgs, explicitApiKey, await readSavedConfigMetadata());
|
|
49
|
+
}
|
|
50
|
+
const savedConfig = await readSavedConfig();
|
|
51
|
+
return buildConfig(parsedArgs, savedConfig?.apiKey ?? null, savedConfig);
|
|
52
|
+
}
|
|
53
|
+
export async function ensureConfig(params) {
|
|
54
|
+
if (params.command === "login") {
|
|
55
|
+
throw new Error("login configures API keys and must run before loading config.");
|
|
56
|
+
}
|
|
57
|
+
return await loadConfig(params.parsedArgs);
|
|
58
|
+
}
|
package/dist/timeouts.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DEFAULT_CLI_REQUEST_TIMEOUT_MS = 30_000;
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { getStringFlag, hasFlag } from "./args.js";
|
|
2
|
+
import { formatMessageLine, getMessageBody, getVisibleText } from "./format.js";
|
|
3
|
+
function getPositiveIntegerFlag(parsedArgs, longName, shortName) {
|
|
4
|
+
const value = getStringFlag(parsedArgs, longName, shortName);
|
|
5
|
+
if (value == null) {
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
const parsed = Number.parseInt(value, 10);
|
|
9
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0 || String(parsed) !== value.trim()) {
|
|
10
|
+
throw new Error(`--${longName} must be a positive integer.`);
|
|
11
|
+
}
|
|
12
|
+
return parsed;
|
|
13
|
+
}
|
|
14
|
+
function getWatchDelivery(parsedArgs) {
|
|
15
|
+
const delivery = getStringFlag(parsedArgs, "delivery");
|
|
16
|
+
if (delivery == null) {
|
|
17
|
+
if (hasFlag(parsedArgs, "all")) {
|
|
18
|
+
throw new Error('Public realtime currently supports --delivery "mentions" only.');
|
|
19
|
+
}
|
|
20
|
+
return "mentions";
|
|
21
|
+
}
|
|
22
|
+
if (delivery !== "mentions") {
|
|
23
|
+
throw new Error('Unknown --delivery value. Expected "mentions".');
|
|
24
|
+
}
|
|
25
|
+
return delivery;
|
|
26
|
+
}
|
|
27
|
+
function getWatchTarget(parsedArgs) {
|
|
28
|
+
const target = parsedArgs.positionals[1] ?? "messages";
|
|
29
|
+
if (target !== "messages") {
|
|
30
|
+
throw new Error(`Unknown watch target "${target}". Expected "messages".`);
|
|
31
|
+
}
|
|
32
|
+
return target;
|
|
33
|
+
}
|
|
34
|
+
function getConversationLabel(event) {
|
|
35
|
+
const prefix = event.isDirectMessage ? "@" : "#";
|
|
36
|
+
const rawName = event.conversation?.name ?? event.message.conversation_name;
|
|
37
|
+
const name = typeof rawName === "string" ? rawName.trim() : "";
|
|
38
|
+
if (name !== "") {
|
|
39
|
+
return `${prefix}${name}`;
|
|
40
|
+
}
|
|
41
|
+
return `${prefix}${event.message.conversation_id}`;
|
|
42
|
+
}
|
|
43
|
+
function writeWatchJsonEvent(event) {
|
|
44
|
+
process.stdout.write(`${JSON.stringify({
|
|
45
|
+
type: "message",
|
|
46
|
+
id: event.message.id,
|
|
47
|
+
conversationId: event.message.conversation_id,
|
|
48
|
+
conversationName: event.conversation?.name ?? event.message.conversation_name,
|
|
49
|
+
isDirectMessage: event.isDirectMessage,
|
|
50
|
+
authorId: event.message.author_id,
|
|
51
|
+
authorName: event.message.author.display_name ?? null,
|
|
52
|
+
content: getVisibleText(getMessageBody(event.message)),
|
|
53
|
+
markdownContent: event.message.markdown_content ?? null,
|
|
54
|
+
createdAt: event.message.created_at,
|
|
55
|
+
threadRootId: event.message.thread_root_id ?? null,
|
|
56
|
+
repliesCount: event.message.replies_count,
|
|
57
|
+
context: event.context,
|
|
58
|
+
})}\n`);
|
|
59
|
+
}
|
|
60
|
+
function writeWatchPlainEvent(event) {
|
|
61
|
+
process.stdout.write(`${event.message.id}\t${getConversationLabel(event)}\t${formatMessageLine(event.message)}\n`);
|
|
62
|
+
}
|
|
63
|
+
export async function runWatchCommand({ apiClient, parsedArgs, isInteractive = Boolean(process.stdin.isTTY && process.stdout.isTTY), }) {
|
|
64
|
+
getWatchTarget(parsedArgs);
|
|
65
|
+
const jsonFlag = hasFlag(parsedArgs, "json");
|
|
66
|
+
const delivery = getWatchDelivery(parsedArgs);
|
|
67
|
+
const limit = getPositiveIntegerFlag(parsedArgs, "limit", "n");
|
|
68
|
+
const timeoutMs = getPositiveIntegerFlag(parsedArgs, "timeout");
|
|
69
|
+
if (!isInteractive && limit == null && timeoutMs == null) {
|
|
70
|
+
throw new Error("watch messages requires --limit or --timeout in non-interactive mode.");
|
|
71
|
+
}
|
|
72
|
+
const controller = new AbortController();
|
|
73
|
+
let messageCount = 0;
|
|
74
|
+
let timeout;
|
|
75
|
+
const stop = (reason) => {
|
|
76
|
+
if (!controller.signal.aborted) {
|
|
77
|
+
controller.abort(new Error(reason));
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const onInterrupt = () => stop("watch interrupted");
|
|
81
|
+
if (timeoutMs != null) {
|
|
82
|
+
timeout = setTimeout(() => stop("watch timeout reached"), timeoutMs);
|
|
83
|
+
}
|
|
84
|
+
process.once("SIGINT", onInterrupt);
|
|
85
|
+
let subscription = null;
|
|
86
|
+
try {
|
|
87
|
+
subscription = await apiClient.realtime.subscribeMember({
|
|
88
|
+
delivery,
|
|
89
|
+
signal: controller.signal,
|
|
90
|
+
onMessage(event) {
|
|
91
|
+
messageCount += 1;
|
|
92
|
+
if (jsonFlag) {
|
|
93
|
+
writeWatchJsonEvent(event);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
writeWatchPlainEvent(event);
|
|
97
|
+
}
|
|
98
|
+
if (limit != null && messageCount >= limit) {
|
|
99
|
+
stop("watch limit reached");
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
if (isInteractive) {
|
|
104
|
+
process.stderr.write("Watching mentions via realtime. Press Ctrl-C to stop.\n");
|
|
105
|
+
}
|
|
106
|
+
await subscription.done();
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
if (!controller.signal.aborted) {
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
if (timeout != null) {
|
|
115
|
+
clearTimeout(timeout);
|
|
116
|
+
}
|
|
117
|
+
process.removeListener("SIGINT", onInterrupt);
|
|
118
|
+
await subscription?.close().catch(() => undefined);
|
|
119
|
+
}
|
|
120
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andocorp/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Ando CLI -
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Ando CLI - agent-first terminal commands",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"ando": "./dist/index.js"
|
|
@@ -15,28 +15,27 @@
|
|
|
15
15
|
"author": "Asari Inc.",
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"files": [
|
|
18
|
-
"dist
|
|
18
|
+
"dist"
|
|
19
19
|
],
|
|
20
20
|
"publishConfig": {
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"@ando/shared": "0.1.0"
|
|
24
|
+
"@napi-rs/keyring": "^1.3.0",
|
|
25
|
+
"@andocorp/sdk": "0.2.0"
|
|
27
26
|
},
|
|
28
27
|
"devDependencies": {
|
|
29
|
-
"@types/node": "^
|
|
30
|
-
"
|
|
31
|
-
"
|
|
28
|
+
"@types/node": "^25.9.1",
|
|
29
|
+
"@vitest/coverage-v8": "^4.1.7",
|
|
30
|
+
"typescript": "^6.0.3",
|
|
31
|
+
"vitest": "^4.1.7"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
|
-
"build": "
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"typecheck": "tsc --noEmit"
|
|
34
|
+
"build": "cd ../sdk && pnpm run build && cd ../cli && rm -rf dist && tsc && chmod +x ./dist/index.js",
|
|
35
|
+
"dev": "pnpm run build && node ./dist/index.js",
|
|
36
|
+
"lint": "tsc --noEmit",
|
|
37
|
+
"test": "cd ../sdk && pnpm run build && cd ../cli && vitest run",
|
|
38
|
+
"test:coverage": "cd ../sdk && pnpm run build && cd ../cli && vitest run --coverage",
|
|
39
|
+
"typecheck": "cd ../sdk && pnpm run build && cd ../cli && tsc --noEmit"
|
|
41
40
|
}
|
|
42
41
|
}
|