noeta-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/README.md +91 -0
- package/cli.mjs +290 -0
- package/package.json +24 -0
package/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# noeta-cli
|
|
2
|
+
|
|
3
|
+
Installs the `noeta` command (`npm i -g noeta-cli` for the short form; `npx noeta-cli …` works with no install).
|
|
4
|
+
|
|
5
|
+
CLI portal to a [Noeta Cloud](https://noeta.cloud) workspace — for agents and
|
|
6
|
+
the humans who authorize them.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npx noeta-cli login
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
That's the whole setup: your browser opens, you sign in, pick an access level
|
|
13
|
+
(full / editor / commenter / read-only), and click **Approve**. The command
|
|
14
|
+
finishes on the click — it never asks for terminal input — and saves the token
|
|
15
|
+
locally. From then on the CLI *is* the integration; no MCP client
|
|
16
|
+
configuration required:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx noeta-cli whoami # who am I, which workspace, what cap
|
|
20
|
+
npx noeta-cli tools # list the workspace's tools
|
|
21
|
+
npx noeta-cli call get_doc '{"docId":"<id>"}' # read a document
|
|
22
|
+
npx noeta-cli call insert_blocks '{"docId":"<id>","blocks":[{"type":"paragraph","content":"hi"}]}'
|
|
23
|
+
npx noeta-cli logout # revoke the token server-side + forget it
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`login` defaults to `https://noeta.cloud`; pass your deployment's URL for
|
|
27
|
+
per-org instances: `npx noeta-cli login https://your-org.noeta.cloud`. Credentials
|
|
28
|
+
live in `~/.config/noeta/credentials.json` (0600), one entry per deployment;
|
|
29
|
+
the last login is the default target and `--url` / `NOETA_URL` switch between
|
|
30
|
+
them. `NOETA_TOKEN` overrides the store entirely (CI).
|
|
31
|
+
|
|
32
|
+
## What the identity is
|
|
33
|
+
|
|
34
|
+
Approving mints a **delegated agent identity**: it acts on *your* behalf,
|
|
35
|
+
capped at the level you approved — it can do at most what you can do, never
|
|
36
|
+
more, and everything it writes is attributed to you *via* the agent. Revoke
|
|
37
|
+
any time with `npx noeta-cli logout` (or `DELETE /api/agents/<id>` as your
|
|
38
|
+
signed-in user).
|
|
39
|
+
|
|
40
|
+
## For agents driving this programmatically
|
|
41
|
+
|
|
42
|
+
No command reads stdin, and `--json` makes stdout a single machine-readable
|
|
43
|
+
value (progress and errors go to stderr):
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npx noeta-cli login --json --no-open # stderr: the approval link to hand to the user
|
|
47
|
+
# stdout: {"token":…,"agentId":…,"mcpUrl":…} on approval
|
|
48
|
+
npx noeta-cli whoami --json
|
|
49
|
+
npx noeta-cli tools --json
|
|
50
|
+
npx noeta-cli call get_doc '{"docId":"<id>"}' # stdout: the tool result JSON
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Exit codes: `0` ok · `1` usage/network/server error · `2` login denied,
|
|
54
|
+
expired, or timed out · `3` the tool call returned an error (reason on stderr).
|
|
55
|
+
|
|
56
|
+
Prefer a native MCP connection? `login` also prints the one-liner:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
claude mcp add --transport http noeta https://your-org.noeta.cloud/mcp \
|
|
60
|
+
--header "Authorization: Bearer noeta_…"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## The raw HTTP contract (no node required)
|
|
64
|
+
|
|
65
|
+
The CLI is a convenience over plain HTTP — any language can do this
|
|
66
|
+
(OAuth-device-grant shaped):
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# 1. start (no auth): returns userCode (for the browser), deviceCode (for you), verifyUrl
|
|
70
|
+
curl -X POST https://your-org.noeta.cloud/api/agent-auth/start \
|
|
71
|
+
-H 'content-type: application/json' -d '{"clientName":"My Agent"}'
|
|
72
|
+
|
|
73
|
+
# 2. send the user to verifyUrl (…/?agent-auth=<userCode>) and let them approve
|
|
74
|
+
|
|
75
|
+
# 3. poll (no auth) every ~2s until "approved"; the token is returned exactly ONCE
|
|
76
|
+
curl -X POST https://your-org.noeta.cloud/api/agent-auth/poll \
|
|
77
|
+
-H 'content-type: application/json' -d '{"deviceCode":"<deviceCode>"}'
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Then, with `Authorization: Bearer <token>`:
|
|
81
|
+
|
|
82
|
+
- `POST /mcp` — MCP over Streamable HTTP (JSON-RPC 2.0): `tools/list`, `tools/call`
|
|
83
|
+
- `GET /agent/me` — the authenticated identity
|
|
84
|
+
- `POST /agent/revoke` — self-revoke (what `logout` calls)
|
|
85
|
+
|
|
86
|
+
Requests expire after 10 minutes; the `userCode` in the URL can't poll the
|
|
87
|
+
token; the server stores only a hash of the token.
|
|
88
|
+
|
|
89
|
+
## Requirements
|
|
90
|
+
|
|
91
|
+
Node ≥ 18 (global `fetch`). Zero dependencies.
|
package/cli.mjs
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// noeta — CLI portal to a Noeta Cloud workspace (BUILD_PLAN §8.7).
|
|
3
|
+
//
|
|
4
|
+
// npx noeta-cli login browser approval → token saved locally
|
|
5
|
+
// npx noeta-cli tools list the workspace's MCP tools
|
|
6
|
+
// npx noeta-cli call get_doc '{"docId":"…"}'
|
|
7
|
+
// npx noeta-cli whoami · logout
|
|
8
|
+
//
|
|
9
|
+
// Built for agents: no command ever reads stdin. `login` opens a browser (or prints
|
|
10
|
+
// the link with --no-open) and finishes the moment the user clicks Approve; every
|
|
11
|
+
// other command runs with the stored token, so an agent with shell access can use
|
|
12
|
+
// the whole workspace with NO MCP client configuration. `--json` makes stdout a
|
|
13
|
+
// single machine-readable value (progress goes to stderr).
|
|
14
|
+
//
|
|
15
|
+
// Credentials: ~/.config/noeta/credentials.json (0600; override the directory with
|
|
16
|
+
// NOETA_CONFIG_DIR). Precedence for authed commands: --url flag > NOETA_URL > the
|
|
17
|
+
// last login; token: NOETA_TOKEN > the stored token for that URL.
|
|
18
|
+
// Exit codes: 0 ok · 1 usage/network/server error · 2 login denied/expired/timed
|
|
19
|
+
// out · 3 the tool call itself returned an error.
|
|
20
|
+
|
|
21
|
+
import { spawn } from "node:child_process";
|
|
22
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
23
|
+
import { join } from "node:path";
|
|
24
|
+
import { homedir } from "node:os";
|
|
25
|
+
import process from "node:process";
|
|
26
|
+
|
|
27
|
+
const DEFAULT_URL = "https://noeta.cloud";
|
|
28
|
+
|
|
29
|
+
const USAGE = `noeta — CLI portal to a Noeta Cloud workspace
|
|
30
|
+
|
|
31
|
+
Usage: noeta <command> [args] [options]
|
|
32
|
+
|
|
33
|
+
Commands:
|
|
34
|
+
login [url] Authorize this machine via a browser approval (no prompts;
|
|
35
|
+
finishes when the user clicks Approve). Saves the token.
|
|
36
|
+
url defaults to ${DEFAULT_URL}; if repeated, the last wins.
|
|
37
|
+
whoami Show the authenticated agent identity + workspace
|
|
38
|
+
tools List the workspace's MCP tools
|
|
39
|
+
call <tool> [json] Call an MCP tool; args as a JSON object (or via --args)
|
|
40
|
+
logout Revoke this machine's token server-side and forget it
|
|
41
|
+
help Show this help
|
|
42
|
+
|
|
43
|
+
Options:
|
|
44
|
+
--url <url> Target a specific saved deployment (default: last login)
|
|
45
|
+
--json Machine-readable stdout (progress/errors → stderr)
|
|
46
|
+
--name <name> [login] Display name on the approval screen (default "MCP agent")
|
|
47
|
+
--no-open [login] Don't launch a browser; just print the approval link
|
|
48
|
+
--timeout <sec> [login] Max seconds to wait (default: the server's window)
|
|
49
|
+
--args <json> [call] Tool arguments as a JSON object
|
|
50
|
+
|
|
51
|
+
Environment: NOETA_URL, NOETA_TOKEN (override the store), NOETA_CONFIG_DIR.
|
|
52
|
+
Exit codes: 0 ok · 1 error · 2 login denied/expired/timed out · 3 tool-call error.
|
|
53
|
+
|
|
54
|
+
The same access over raw HTTP (any language): POST /api/agent-auth/start → browser
|
|
55
|
+
approves at verifyUrl → POST /api/agent-auth/poll → Bearer token for POST /mcp.`;
|
|
56
|
+
|
|
57
|
+
// ── arg parsing (no deps) ─────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
const argv = process.argv.slice(2);
|
|
60
|
+
const opts = { json: false, open: true, name: "MCP agent", url: null, timeout: null, args: null };
|
|
61
|
+
const positionals = [];
|
|
62
|
+
for (let i = 0; i < argv.length; i++) {
|
|
63
|
+
const a = argv[i];
|
|
64
|
+
if (a === "-h" || a === "--help") {
|
|
65
|
+
console.log(USAGE);
|
|
66
|
+
process.exit(0);
|
|
67
|
+
} else if (a === "--json") opts.json = true;
|
|
68
|
+
else if (a === "--no-open") opts.open = false;
|
|
69
|
+
else if (a === "--name") opts.name = argv[++i] ?? opts.name;
|
|
70
|
+
else if (a === "--url") opts.url = argv[++i] ?? null;
|
|
71
|
+
else if (a === "--timeout") opts.timeout = Number(argv[++i]);
|
|
72
|
+
else if (a === "--args") opts.args = argv[++i] ?? null;
|
|
73
|
+
else if (a.startsWith("-")) die(`Unknown option: ${a}\n\n${USAGE}`);
|
|
74
|
+
else positionals.push(a);
|
|
75
|
+
}
|
|
76
|
+
const command = positionals.shift() ?? "help";
|
|
77
|
+
|
|
78
|
+
// In --json mode stdout is reserved for the final value; everything else → stderr.
|
|
79
|
+
const log = (msg) => (opts.json ? process.stderr : process.stdout).write(msg);
|
|
80
|
+
const emit = (human, jsonValue) =>
|
|
81
|
+
opts.json ? process.stdout.write(`${JSON.stringify(jsonValue)}\n`) : console.log(human);
|
|
82
|
+
|
|
83
|
+
function die(msg, code = 1) {
|
|
84
|
+
process.stderr.write(`✖ ${msg}\n`);
|
|
85
|
+
process.exit(code);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── credential store ──────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
const configDir = () => process.env.NOETA_CONFIG_DIR ?? join(homedir(), ".config", "noeta");
|
|
91
|
+
const credsPath = () => join(configDir(), "credentials.json");
|
|
92
|
+
|
|
93
|
+
function readCreds() {
|
|
94
|
+
try {
|
|
95
|
+
return JSON.parse(readFileSync(credsPath(), "utf8"));
|
|
96
|
+
} catch {
|
|
97
|
+
return { default: null, origins: {} };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function writeCreds(creds) {
|
|
102
|
+
mkdirSync(configDir(), { recursive: true });
|
|
103
|
+
writeFileSync(credsPath(), `${JSON.stringify(creds, null, 2)}\n`, { mode: 0o600 });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Resolve the deployment + token an authed command should use. */
|
|
107
|
+
function resolveSession() {
|
|
108
|
+
const creds = readCreds();
|
|
109
|
+
const url = (opts.url ?? process.env.NOETA_URL ?? creds.default ?? "").replace(/\/$/, "");
|
|
110
|
+
if (!url) die(`no deployment selected — run \`npx noeta-cli login\` first (or pass --url / set NOETA_URL)`);
|
|
111
|
+
const token = process.env.NOETA_TOKEN ?? creds.origins?.[url]?.token;
|
|
112
|
+
if (!token) die(`no token for ${url} — run \`npx noeta-cli login ${url}\` (or set NOETA_TOKEN)`);
|
|
113
|
+
return { url, token, creds };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ── HTTP helpers ──────────────────────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
async function post(baseUrl, path, body, headers = {}) {
|
|
119
|
+
try {
|
|
120
|
+
return await fetch(`${baseUrl}${path}`, {
|
|
121
|
+
method: "POST",
|
|
122
|
+
headers: { "Content-Type": "application/json", ...headers },
|
|
123
|
+
body: JSON.stringify(body),
|
|
124
|
+
});
|
|
125
|
+
} catch (err) {
|
|
126
|
+
die(`could not reach ${baseUrl} (${err.message}). Is the URL right and the server up?`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let rpcId = 0;
|
|
131
|
+
async function mcp(session, method, params) {
|
|
132
|
+
const res = await post(session.url, "/mcp", { jsonrpc: "2.0", id: ++rpcId, method, params }, {
|
|
133
|
+
Authorization: `Bearer ${session.token}`,
|
|
134
|
+
});
|
|
135
|
+
if (res.status === 401) die(`unauthorized — the token for ${session.url} was revoked or expired. Run \`npx noeta-cli login ${session.url}\`.`);
|
|
136
|
+
if (!res.ok) die(`${method} failed: HTTP ${res.status} ${await res.text()}`);
|
|
137
|
+
const body = await res.json();
|
|
138
|
+
if (body.error) die(`${method} failed: ${body.error.message ?? JSON.stringify(body.error)}`);
|
|
139
|
+
return body.result;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Best-effort browser open; the printed URL is always the fallback (headless/SSH). */
|
|
143
|
+
function openBrowser(url) {
|
|
144
|
+
const [cmd, args] =
|
|
145
|
+
process.platform === "darwin"
|
|
146
|
+
? ["open", [url]]
|
|
147
|
+
: process.platform === "win32"
|
|
148
|
+
? ["cmd", ["/c", "start", "", url]]
|
|
149
|
+
: ["xdg-open", [url]];
|
|
150
|
+
try {
|
|
151
|
+
spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
|
|
152
|
+
} catch {
|
|
153
|
+
/* fall back to the printed URL */
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ── commands ──────────────────────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
async function cmdLogin() {
|
|
160
|
+
// Last positional wins so npm scripts can bake in a default the caller overrides.
|
|
161
|
+
const baseUrl = (positionals.pop() ?? DEFAULT_URL).replace(/\/$/, "");
|
|
162
|
+
|
|
163
|
+
const startRes = await post(baseUrl, "/api/agent-auth/start", { clientName: opts.name });
|
|
164
|
+
if (!startRes.ok) die(`start failed: HTTP ${startRes.status} ${await startRes.text()}`);
|
|
165
|
+
const { deviceCode, verifyUrl, expiresIn, interval } = await startRes.json();
|
|
166
|
+
|
|
167
|
+
const windowSec = Number.isFinite(opts.timeout) && opts.timeout > 0 ? opts.timeout : expiresIn;
|
|
168
|
+
log(`\nAuthorize ${opts.name} in your browser (link expires in ${Math.round(expiresIn / 60)} min):\n`);
|
|
169
|
+
log(`\n ${verifyUrl}\n\n`);
|
|
170
|
+
if (opts.open) openBrowser(verifyUrl);
|
|
171
|
+
log("Waiting for approval…");
|
|
172
|
+
|
|
173
|
+
const deadline = Date.now() + windowSec * 1000;
|
|
174
|
+
let approval = null;
|
|
175
|
+
while (Date.now() < deadline) {
|
|
176
|
+
await new Promise((r) => setTimeout(r, (interval ?? 2) * 1000));
|
|
177
|
+
const res = await post(baseUrl, "/api/agent-auth/poll", { deviceCode });
|
|
178
|
+
if (res.status === 404) die("the request expired or was denied. Re-run to try again.", 2);
|
|
179
|
+
if (!res.ok) die(`poll failed: HTTP ${res.status}`);
|
|
180
|
+
const body = await res.json();
|
|
181
|
+
if (body.status === "approved") {
|
|
182
|
+
approval = body;
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
log(".");
|
|
186
|
+
}
|
|
187
|
+
if (!approval) die("timed out waiting for approval. Re-run to try again.", 2);
|
|
188
|
+
log(" approved ✔\n");
|
|
189
|
+
|
|
190
|
+
const { token, agentId, agentName, workspaceId } = approval;
|
|
191
|
+
const creds = readCreds();
|
|
192
|
+
creds.origins = { ...creds.origins, [baseUrl]: { token, agentId, agentName, workspaceId } };
|
|
193
|
+
creds.default = baseUrl;
|
|
194
|
+
writeCreds(creds);
|
|
195
|
+
|
|
196
|
+
const mcpUrl = `${baseUrl}/mcp`;
|
|
197
|
+
if (opts.json) {
|
|
198
|
+
emit(null, { token, agentId, agentName, workspaceId, mcpUrl, baseUrl });
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
console.log(`\nConnected as ${agentName} (${agentId}) — workspace ${workspaceId}.`);
|
|
202
|
+
console.log(`Token saved to ${credsPath()} (shown below once; the server stores only a hash).\n`);
|
|
203
|
+
console.log(`Use it straight from this CLI — no MCP configuration needed:`);
|
|
204
|
+
console.log(` npx noeta-cli tools`);
|
|
205
|
+
console.log(` npx noeta-cli call get_doc '{"docId":"<id>"}'\n`);
|
|
206
|
+
console.log(`Or wire up an MCP client:`);
|
|
207
|
+
console.log(` claude mcp add --transport http noeta ${mcpUrl} --header "Authorization: Bearer ${token}"\n`);
|
|
208
|
+
console.log(`Disconnect any time with: npx noeta-cli logout`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function cmdWhoami() {
|
|
212
|
+
const session = resolveSession();
|
|
213
|
+
const res = await fetch(`${session.url}/agent/me`, { headers: { Authorization: `Bearer ${session.token}` } });
|
|
214
|
+
if (res.status === 401) die(`unauthorized — run \`npx noeta-cli login ${session.url}\``);
|
|
215
|
+
if (!res.ok) die(`whoami failed: HTTP ${res.status}`);
|
|
216
|
+
const me = await res.json();
|
|
217
|
+
emit(
|
|
218
|
+
`${me.name} (${me.agentId})\n` +
|
|
219
|
+
` deployment: ${session.url}\n` +
|
|
220
|
+
` workspace: ${me.workspaceId ?? "—"}\n` +
|
|
221
|
+
` acting for: ${me.delegatedUserId ?? "— (standalone service account)"}\n` +
|
|
222
|
+
` access cap: ${me.roleCap ?? "none (the user's full role)"}`,
|
|
223
|
+
{ ...me, baseUrl: session.url },
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function cmdTools() {
|
|
228
|
+
const session = resolveSession();
|
|
229
|
+
const result = await mcp(session, "tools/list", {});
|
|
230
|
+
const tools = result.tools ?? [];
|
|
231
|
+
if (opts.json) return emit(null, tools);
|
|
232
|
+
for (const t of tools) {
|
|
233
|
+
const first = (t.description ?? "").split(". ")[0];
|
|
234
|
+
console.log(`${t.name.padEnd(16)} ${first}`);
|
|
235
|
+
}
|
|
236
|
+
console.log(`\n${tools.length} tools. Call one with: npx noeta-cli call <tool> '<json-args>'`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function cmdCall() {
|
|
240
|
+
const name = positionals.shift();
|
|
241
|
+
if (!name) die(`usage: noeta call <tool> ['{"json":"args"}']`);
|
|
242
|
+
const raw = opts.args ?? positionals.shift() ?? "{}";
|
|
243
|
+
let args;
|
|
244
|
+
try {
|
|
245
|
+
args = JSON.parse(raw);
|
|
246
|
+
} catch {
|
|
247
|
+
die(`tool arguments must be a JSON object, got: ${raw}`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const session = resolveSession();
|
|
251
|
+
const result = await mcp(session, "tools/call", { name, arguments: args });
|
|
252
|
+
const text = (result.content ?? [])
|
|
253
|
+
.filter((c) => c.type === "text")
|
|
254
|
+
.map((c) => c.text)
|
|
255
|
+
.join("\n");
|
|
256
|
+
if (result.isError) {
|
|
257
|
+
process.stderr.write(`✖ ${text}\n`);
|
|
258
|
+
process.exit(3);
|
|
259
|
+
}
|
|
260
|
+
// Tool results are already JSON text — print raw so output is pipeable either way.
|
|
261
|
+
process.stdout.write(`${text}\n`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async function cmdLogout() {
|
|
265
|
+
const session = resolveSession();
|
|
266
|
+
// Revoke server-side first (best effort — the local forget still happens).
|
|
267
|
+
const res = await fetch(`${session.url}/agent/revoke`, {
|
|
268
|
+
method: "POST",
|
|
269
|
+
headers: { Authorization: `Bearer ${session.token}` },
|
|
270
|
+
});
|
|
271
|
+
const revoked = res.ok;
|
|
272
|
+
if (!revoked) process.stderr.write(`⚠ server-side revoke failed (HTTP ${res.status}) — token forgotten locally only\n`);
|
|
273
|
+
|
|
274
|
+
const creds = session.creds;
|
|
275
|
+
delete creds.origins?.[session.url];
|
|
276
|
+
if (creds.default === session.url) creds.default = Object.keys(creds.origins ?? {})[0] ?? null;
|
|
277
|
+
writeCreds(creds);
|
|
278
|
+
emit(`Disconnected from ${session.url}${revoked ? " (token revoked)" : ""}.`, { loggedOut: session.url, revoked });
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ── dispatch ──────────────────────────────────────────────────────────────────
|
|
282
|
+
|
|
283
|
+
const commands = { login: cmdLogin, whoami: cmdWhoami, tools: cmdTools, call: cmdCall, logout: cmdLogout };
|
|
284
|
+
if (command === "help") {
|
|
285
|
+
console.log(USAGE);
|
|
286
|
+
} else if (commands[command]) {
|
|
287
|
+
await commands[command]();
|
|
288
|
+
} else {
|
|
289
|
+
die(`unknown command: ${command}\n\n${USAGE}`);
|
|
290
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "noeta-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI portal to Noeta Cloud for agents & humans: `npx noeta-cli login` (browser approval, no prompts), then call the workspace API/MCP tools directly — no MCP client configuration required.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"noeta": "./cli.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"cli.mjs",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"agent",
|
|
19
|
+
"cli",
|
|
20
|
+
"oauth-device-flow",
|
|
21
|
+
"noeta"
|
|
22
|
+
],
|
|
23
|
+
"license": "MIT"
|
|
24
|
+
}
|