@sudoji/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.
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
+ }) : x)(function(x) {
6
+ if (typeof require !== "undefined") return require.apply(this, arguments);
7
+ throw Error('Dynamic require of "' + x + '" is not supported');
8
+ });
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
12
+ var __commonJS = (cb, mod) => function __require2() {
13
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
14
+ };
15
+
16
+ export {
17
+ __require,
18
+ __esm,
19
+ __commonJS
20
+ };
package/dist/index.js ADDED
@@ -0,0 +1,493 @@
1
+ #!/usr/bin/env node
2
+ import "./chunk-RWRJASDG.js";
3
+
4
+ // src/index.ts
5
+ import { Command as Command7 } from "commander";
6
+
7
+ // src/commands/login.ts
8
+ import { Command } from "commander";
9
+ import { createInterface } from "readline/promises";
10
+ import { stdin, stdout } from "process";
11
+ import { isSudojiKey } from "@sudoji/core";
12
+
13
+ // src/auth/credentials.ts
14
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from "fs";
15
+ import { join, dirname } from "path";
16
+ import { homedir } from "os";
17
+ var SERVICE = "sudoji";
18
+ async function tryKeytar() {
19
+ if (process.env["SUDOJI_NO_KEYCHAIN"]) return null;
20
+ try {
21
+ const k = await import("./keytar-2QOTTVWZ.js");
22
+ const mod = "default" in k ? k.default : k;
23
+ return mod ?? null;
24
+ } catch {
25
+ return null;
26
+ }
27
+ }
28
+ function sudojiDir() {
29
+ return process.env["SUDOJI_HOME"] ?? join(homedir(), ".sudoji");
30
+ }
31
+ function filePath(profile) {
32
+ const suffix = profile === "default" ? "" : `.${profile}`;
33
+ return join(sudojiDir(), `credentials${suffix}`);
34
+ }
35
+ function ensureDir(path) {
36
+ mkdirSync(dirname(path), { recursive: true });
37
+ }
38
+ function readFile(path) {
39
+ if (!existsSync(path)) return null;
40
+ try {
41
+ return JSON.parse(readFileSync(path, "utf8"));
42
+ } catch {
43
+ return null;
44
+ }
45
+ }
46
+ function writeFile(path, creds) {
47
+ ensureDir(path);
48
+ writeFileSync(path, JSON.stringify(creds), { mode: 384 });
49
+ chmodSync(path, 384);
50
+ }
51
+ async function store(creds, profile = "default") {
52
+ const keytar = await tryKeytar();
53
+ if (keytar) {
54
+ await keytar.setPassword(SERVICE, profile, JSON.stringify(creds));
55
+ return;
56
+ }
57
+ writeFile(filePath(profile), creds);
58
+ }
59
+ async function load(profile = "default") {
60
+ const keytar = await tryKeytar();
61
+ if (keytar) {
62
+ const raw = await keytar.getPassword(SERVICE, profile);
63
+ if (!raw) return null;
64
+ try {
65
+ return JSON.parse(raw);
66
+ } catch {
67
+ return null;
68
+ }
69
+ }
70
+ return readFile(filePath(profile));
71
+ }
72
+ async function clear(profile = "default") {
73
+ const keytar = await tryKeytar();
74
+ if (keytar) {
75
+ await keytar.deletePassword(SERVICE, profile);
76
+ return;
77
+ }
78
+ const path = filePath(profile);
79
+ if (existsSync(path)) {
80
+ writeFileSync(path, "{}", { mode: 384 });
81
+ }
82
+ }
83
+
84
+ // src/commands/login.ts
85
+ function loginCommand() {
86
+ return new Command("login").description("Authenticate the Sudoji CLI with an API key").option("--paste <key>", "Accept a key directly \u2014 useful for CI and scripting").option("--profile <name>", "Credentials profile to store the key under", "default").action(async (opts) => {
87
+ let key;
88
+ if (opts.paste) {
89
+ key = opts.paste.trim();
90
+ } else {
91
+ console.log("1. Open https://sudoji.io/dashboard/settings");
92
+ console.log("2. Create a new API key (read scope is enough for `sudoji ask`)");
93
+ console.log("3. Paste the key below:");
94
+ const rl = createInterface({ input: stdin, output: stdout });
95
+ key = (await rl.question("\nAPI key: ")).trim();
96
+ rl.close();
97
+ }
98
+ if (!isSudojiKey(key)) {
99
+ console.error('Invalid key \u2014 keys start with "sudoji_sk_" and are at least 26 characters.');
100
+ process.exit(1);
101
+ }
102
+ await store(key, opts.profile);
103
+ console.log(`
104
+ Logged in. Key stored for profile "${opts.profile}".`);
105
+ console.log("Run: sudoji doctor to verify the connection.");
106
+ });
107
+ }
108
+
109
+ // src/commands/logout.ts
110
+ import { Command as Command2 } from "commander";
111
+ function logoutCommand() {
112
+ return new Command2("logout").description("Remove stored credentials for a profile").option("--profile <name>", "Credentials profile to clear", "default").action(async (opts) => {
113
+ await clear(opts.profile);
114
+ console.log(`Credentials cleared for profile "${opts.profile}".`);
115
+ });
116
+ }
117
+
118
+ // src/commands/doctor.ts
119
+ import { Command as Command3 } from "commander";
120
+ import { isSudojiKey as isSudojiKey2, redact } from "@sudoji/core";
121
+
122
+ // src/config/load.ts
123
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
124
+ import { join as join2 } from "path";
125
+ import { homedir as homedir2 } from "os";
126
+ import { SettingsSchema, LOCKABLE_FIELDS } from "@sudoji/core";
127
+ function tryReadJson(path) {
128
+ if (!existsSync2(path)) return {};
129
+ try {
130
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
131
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
132
+ } catch {
133
+ return {};
134
+ }
135
+ }
136
+ function deepMerge(base, over) {
137
+ const result = { ...base };
138
+ for (const [k, v] of Object.entries(over)) {
139
+ const b = base[k];
140
+ if (typeof v === "object" && v !== null && !Array.isArray(v) && typeof b === "object" && b !== null && !Array.isArray(b)) {
141
+ result[k] = deepMerge(b, v);
142
+ } else {
143
+ result[k] = v;
144
+ }
145
+ }
146
+ return result;
147
+ }
148
+ function getAt(obj, path) {
149
+ let cur = obj;
150
+ for (const key of path) {
151
+ if (typeof cur !== "object" || cur === null) return void 0;
152
+ cur = cur[key];
153
+ }
154
+ return cur;
155
+ }
156
+ function setAt(obj, path, value) {
157
+ const [head, ...rest] = path;
158
+ if (head === void 0) return obj;
159
+ if (rest.length === 0) return { ...obj, [head]: value };
160
+ const child = obj[head] ?? {};
161
+ return { ...obj, [head]: setAt(child, rest, value) };
162
+ }
163
+ function enforceOrgLock(merged, org) {
164
+ let result = merged;
165
+ const orgDeny = getAt(org, ["permissions", "deny"]) ?? [];
166
+ if (orgDeny.length > 0) {
167
+ const currentDeny = getAt(result, ["permissions", "deny"]) ?? [];
168
+ result = setAt(result, ["permissions", "deny"], [.../* @__PURE__ */ new Set([...orgDeny, ...currentDeny])]);
169
+ }
170
+ for (const field of LOCKABLE_FIELDS) {
171
+ if (field === "permissions.deny") continue;
172
+ const parts = field.split(".");
173
+ const orgVal = getAt(org, parts);
174
+ if (orgVal !== void 0) {
175
+ result = setAt(result, parts, orgVal);
176
+ }
177
+ }
178
+ return result;
179
+ }
180
+ function loadSettings(opts = {}) {
181
+ const cwd = opts.cwd ?? process.cwd();
182
+ const orgPath = opts.orgPath ?? "/etc/sudoji/managed.json";
183
+ const userPath = opts.userPath ?? join2(homedir2(), ".sudoji", "settings.json");
184
+ const org = tryReadJson(orgPath);
185
+ const user = tryReadJson(userPath);
186
+ const project = tryReadJson(join2(cwd, ".sudoji", "settings.json"));
187
+ const projectLocal = tryReadJson(join2(cwd, ".sudoji", "settings.local.json"));
188
+ let merged = deepMerge(org, user);
189
+ merged = deepMerge(merged, project);
190
+ merged = deepMerge(merged, projectLocal);
191
+ merged = enforceOrgLock(merged, org);
192
+ return SettingsSchema.parse(merged);
193
+ }
194
+
195
+ // src/commands/doctor.ts
196
+ function doctorCommand() {
197
+ return new Command3("doctor").description("Verify auth, connectivity, settings, and redaction").option("--profile <name>", "Credentials profile to check", "default").option("--server <url>", "Override API base URL for this check").action(async (opts) => {
198
+ const checks = [];
199
+ const key = await load(opts.profile);
200
+ const keyOk = key !== null && isSudojiKey2(key);
201
+ checks.push({
202
+ label: "API key stored",
203
+ ok: keyOk,
204
+ detail: keyOk ? `${key.slice(0, 18)}\u2026` : `no key for profile "${opts.profile}" \u2014 run: sudoji login`
205
+ });
206
+ let settings = null;
207
+ try {
208
+ settings = loadSettings();
209
+ checks.push({ label: "Settings loaded", ok: true, detail: `apiBaseUrl: ${settings.apiBaseUrl}` });
210
+ } catch (err) {
211
+ checks.push({ label: "Settings loaded", ok: false, detail: String(err) });
212
+ }
213
+ const baseUrl = opts.server ?? settings?.apiBaseUrl ?? "https://api.sudoji.io";
214
+ try {
215
+ const res = await fetch(`${baseUrl}/healthz`, { signal: AbortSignal.timeout(5e3) });
216
+ checks.push({
217
+ label: "API reachable",
218
+ ok: res.ok,
219
+ detail: `${baseUrl}/healthz \u2192 HTTP ${res.status}`
220
+ });
221
+ } catch (err) {
222
+ checks.push({
223
+ label: "API reachable",
224
+ ok: false,
225
+ detail: `${baseUrl}/healthz \u2014 ${err instanceof Error ? err.message : err}`
226
+ });
227
+ }
228
+ const testInput = "password=super$ecret TOKEN=abc123 STRIPE_KEY=sk_live_xxx";
229
+ const testRedacted = redact(testInput);
230
+ const redactionWorking = !testRedacted.includes("super$ecret") && !testRedacted.includes("abc123");
231
+ checks.push({
232
+ label: "Redaction active",
233
+ ok: redactionWorking,
234
+ detail: `"${testInput.slice(0, 20)}\u2026" \u2192 "${testRedacted.slice(0, 30)}\u2026"`
235
+ });
236
+ const ok = (c) => c.ok ? "\u2713" : "\u2717";
237
+ for (const c of checks) {
238
+ console.log(` ${ok(c)} ${c.label}${c.detail ? `
239
+ ${c.detail}` : ""}`);
240
+ }
241
+ const allOk = checks.every((c) => c.ok);
242
+ if (allOk) {
243
+ console.log("\nAll checks passed.");
244
+ } else {
245
+ console.log("\nSome checks failed \u2014 see above.");
246
+ process.exit(1);
247
+ }
248
+ });
249
+ }
250
+
251
+ // src/commands/ask.ts
252
+ import { Command as Command4 } from "commander";
253
+
254
+ // src/context/gather.ts
255
+ import { execSync } from "child_process";
256
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
257
+ import { join as join3 } from "path";
258
+ import os from "os";
259
+ import { redact as redact2 } from "@sudoji/core";
260
+ function tryExec(cmd, cwd, timeout = 4e3) {
261
+ try {
262
+ return execSync(cmd, { encoding: "utf8", cwd, timeout }).trim();
263
+ } catch {
264
+ return void 0;
265
+ }
266
+ }
267
+ function gatherContext(opts = {}) {
268
+ const cwd = opts.cwd ?? process.cwd();
269
+ const maxLogLines = opts.maxLogLines ?? 200;
270
+ const maxFileBytes = opts.maxFileBytes ?? 8192;
271
+ const versions = { node: process.version };
272
+ const npmVer = tryExec("npm --version");
273
+ if (npmVer) versions["npm"] = npmVer;
274
+ let git;
275
+ const branch = tryExec("git branch --show-current", cwd);
276
+ const status = tryExec("git status --short", cwd);
277
+ if (branch !== void 0 || status !== void 0) {
278
+ git = redact2(`branch: ${branch ?? "unknown"}
279
+ ${status ?? ""}`);
280
+ }
281
+ const files = {};
282
+ const pkgPath = join3(cwd, "package.json");
283
+ if (existsSync3(pkgPath)) {
284
+ try {
285
+ const raw = readFileSync3(pkgPath, "utf8").slice(0, maxFileBytes);
286
+ files["package.json"] = redact2(raw);
287
+ } catch {
288
+ }
289
+ }
290
+ const lastStderr = process.env["SUDOJI_LAST_STDERR"];
291
+ const lastExitEnv = process.env["SUDOJI_LAST_EXIT"];
292
+ let logs;
293
+ if (lastStderr) {
294
+ const trimmed = lastStderr.split("\n").slice(-maxLogLines).join("\n");
295
+ logs = redact2(trimmed);
296
+ }
297
+ return {
298
+ cwd,
299
+ os: `${os.platform()} ${os.release()} (${os.arch()})`,
300
+ shell: process.env["SHELL"],
301
+ versions,
302
+ git,
303
+ lastExitCode: lastExitEnv ? parseInt(lastExitEnv, 10) : void 0,
304
+ logs: logs || void 0,
305
+ files: Object.keys(files).length > 0 ? files : void 0
306
+ };
307
+ }
308
+
309
+ // src/client.ts
310
+ async function* readSSELines(res) {
311
+ const reader = res.body.getReader();
312
+ const decoder = new TextDecoder();
313
+ let buffer = "";
314
+ while (true) {
315
+ const { done, value } = await reader.read();
316
+ if (done) break;
317
+ buffer += decoder.decode(value, { stream: true });
318
+ const parts = buffer.split("\n\n");
319
+ buffer = parts.pop() ?? "";
320
+ for (const part of parts) {
321
+ for (const line of part.split("\n")) {
322
+ if (line.startsWith("data: ")) {
323
+ yield line.slice(6);
324
+ }
325
+ }
326
+ }
327
+ }
328
+ for (const line of buffer.split("\n")) {
329
+ if (line.startsWith("data: ")) yield line.slice(6);
330
+ }
331
+ }
332
+ async function* streamAgent(baseUrl, request, token) {
333
+ let res;
334
+ try {
335
+ res = await fetch(`${baseUrl}/v1/agent`, {
336
+ method: "POST",
337
+ headers: {
338
+ "Content-Type": "application/json",
339
+ Authorization: `Bearer ${token}`
340
+ },
341
+ body: JSON.stringify(request)
342
+ });
343
+ } catch (err) {
344
+ throw new Error(
345
+ `Cannot reach ${baseUrl} \u2014 is the agent service running?
346
+ ${err instanceof Error ? err.message : err}`
347
+ );
348
+ }
349
+ if (!res.ok) {
350
+ const body = await res.json().catch(() => ({ error: res.statusText }));
351
+ throw new Error(`Agent API ${res.status}: ${body.error ?? res.statusText}`);
352
+ }
353
+ for await (const data of readSSELines(res)) {
354
+ if (!data.trim()) continue;
355
+ try {
356
+ yield JSON.parse(data);
357
+ } catch {
358
+ }
359
+ }
360
+ }
361
+
362
+ // src/commands/ask.ts
363
+ function askCommand() {
364
+ return new Command4("ask").description("Ask Sudoji Agent a question about your server").argument("[question...]", "Question to ask (reads from stdin if omitted)").option("--no-context", "Skip automatic context gathering").option("--mode <mode>", "Agent mode: diagnose | suggest | fix", "diagnose").option("--max-steps <n>", "Maximum agent steps", "20").option("--server <url>", "Override API base URL").option("--profile <name>", "Credentials profile", "default").option("--json", "Emit events as newline-delimited JSON instead of plain text").action(async (questionParts, opts) => {
365
+ const question = questionParts.join(" ").trim();
366
+ if (!question) {
367
+ console.error('Usage: sudoji ask "why is nginx not starting?"');
368
+ process.exit(1);
369
+ }
370
+ const key = await load(opts.profile);
371
+ if (!key) {
372
+ console.error("Not logged in. Run: sudoji login");
373
+ process.exit(1);
374
+ }
375
+ const settings = loadSettings();
376
+ const baseUrl = opts.server ?? settings.apiBaseUrl;
377
+ const context = opts.context ? gatherContext({ maxLogLines: settings.context.maxLogLines, maxFileBytes: settings.context.maxFileBytes }) : { cwd: process.cwd(), os: process.platform };
378
+ const stream = streamAgent(
379
+ baseUrl,
380
+ {
381
+ message: question,
382
+ mode: opts.mode,
383
+ context,
384
+ maxSteps: Math.min(parseInt(opts.maxSteps, 10), 50)
385
+ },
386
+ key
387
+ );
388
+ if (!opts.json) process.stdout.write("\n");
389
+ for await (const event of stream) {
390
+ if (opts.json) {
391
+ console.log(JSON.stringify(event));
392
+ continue;
393
+ }
394
+ if (event.type === "text-delta") process.stdout.write(event.text);
395
+ if (event.type === "final") process.stdout.write("\n");
396
+ if (event.type === "error") {
397
+ console.error(`
398
+ [${event.code}] ${event.message}`);
399
+ process.exit(1);
400
+ }
401
+ }
402
+ });
403
+ }
404
+
405
+ // src/commands/logs.ts
406
+ import { Command as Command5 } from "commander";
407
+ import { execSync as execSync2 } from "child_process";
408
+ import { redact as redact3 } from "@sudoji/core";
409
+ function fetchLogs(opts) {
410
+ const n = parseInt(opts.tail, 10);
411
+ switch (opts.source) {
412
+ case "pm2": {
413
+ const unit = opts.unit ?? "all";
414
+ return execSync2(`pm2 logs ${unit} --nostream --lines ${n}`, { encoding: "utf8", timeout: 1e4 });
415
+ }
416
+ case "systemd": {
417
+ const unit = opts.unit ? `-u ${opts.unit}` : "";
418
+ const window = opts.since ? `--since "${opts.since}"` : `-n ${n}`;
419
+ return execSync2(`journalctl ${unit} ${window} --no-pager --output=short`, { encoding: "utf8", timeout: 1e4 });
420
+ }
421
+ case "file": {
422
+ if (!opts.file) throw new Error("--file <path> is required when --source=file");
423
+ return execSync2(`tail -n ${n} "${opts.file}"`, { encoding: "utf8", timeout: 5e3 });
424
+ }
425
+ default:
426
+ throw new Error(`Unknown source "${opts.source}". Use pm2 | systemd | file.`);
427
+ }
428
+ }
429
+ function logsCommand() {
430
+ return new Command5("logs").description("Fetch and analyse logs from pm2, systemd, or a file").option("--source <type>", "Log source: pm2 | systemd | file", "systemd").option("--unit <name>", "pm2 app name or systemd unit (e.g. nginx)").option("--tail <n>", "Lines to fetch", "100").option("--since <time>", 'Time expression, e.g. "2 hours ago" (systemd only)').option("--file <path>", "Path to a log file (--source=file)").option("--ask <question>", "Question to ask about the logs", "Analyse these logs and identify any errors, warnings, or anomalies.").option("--server <url>", "Override API base URL").option("--profile <name>", "Credentials profile", "default").action(async (opts) => {
431
+ let raw;
432
+ try {
433
+ raw = fetchLogs(opts);
434
+ } catch (err) {
435
+ console.error(`Failed to fetch logs: ${err instanceof Error ? err.message : err}`);
436
+ process.exit(1);
437
+ }
438
+ const redacted = redact3(raw);
439
+ const message = `${opts.ask}
440
+
441
+ <logs source="${opts.source}">
442
+ ${redacted}
443
+ </logs>`;
444
+ const key = await load(opts.profile);
445
+ if (!key) {
446
+ console.error("Not logged in. Run: sudoji login");
447
+ process.exit(1);
448
+ }
449
+ const settings = loadSettings();
450
+ const baseUrl = opts.server ?? settings.apiBaseUrl;
451
+ const context = gatherContext();
452
+ context.logs = redacted;
453
+ const stream = streamAgent(baseUrl, { message, mode: "diagnose", context, maxSteps: 10 }, key);
454
+ process.stdout.write("\n");
455
+ for await (const event of stream) {
456
+ if (event.type === "text-delta") process.stdout.write(event.text);
457
+ if (event.type === "final") process.stdout.write("\n");
458
+ if (event.type === "error") {
459
+ console.error(`
460
+ Error: ${event.message}`);
461
+ process.exit(1);
462
+ }
463
+ }
464
+ });
465
+ }
466
+
467
+ // src/commands/keys.ts
468
+ import { Command as Command6 } from "commander";
469
+ function keysCommand() {
470
+ const keys = new Command6("keys").description("Manage Sudoji Agent API keys");
471
+ keys.command("list").description("List your API keys (opens the dashboard)").action(() => {
472
+ console.log("Open https://sudoji.io/dashboard/settings to manage your API keys.");
473
+ console.log("Key management via the CLI is available in a future release.");
474
+ });
475
+ keys.command("create").description("Create a new API key (opens the dashboard)").action(() => {
476
+ console.log("Open https://sudoji.io/dashboard/settings to create an API key.");
477
+ console.log("Paste the key with: sudoji login --paste <key>");
478
+ });
479
+ keys.command("revoke <id>").description("Revoke an API key (opens the dashboard)").action(() => {
480
+ console.log("Open https://sudoji.io/dashboard/settings to revoke a key.");
481
+ });
482
+ return keys;
483
+ }
484
+
485
+ // src/index.ts
486
+ var program = new Command7().name("sudoji").description("Sudoji Agent \u2014 AI-powered IT operations for Linux servers").version("0.1.0", "--version", "print version and exit");
487
+ program.addCommand(loginCommand());
488
+ program.addCommand(logoutCommand());
489
+ program.addCommand(doctorCommand());
490
+ program.addCommand(askCommand());
491
+ program.addCommand(logsCommand());
492
+ program.addCommand(keysCommand());
493
+ program.parse(process.argv);
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ __commonJS,
4
+ __esm,
5
+ __require
6
+ } from "./chunk-RWRJASDG.js";
7
+
8
+ // ../../node_modules/keytar/build/Release/keytar.node
9
+ var keytar_default;
10
+ var init_keytar = __esm({
11
+ "../../node_modules/keytar/build/Release/keytar.node"() {
12
+ keytar_default = "./keytar-VMICNFEJ.node";
13
+ }
14
+ });
15
+
16
+ // node-file:/home/azureuser/sudoji-agent/node_modules/keytar/build/Release/keytar.node
17
+ var require_keytar = __commonJS({
18
+ "node-file:/home/azureuser/sudoji-agent/node_modules/keytar/build/Release/keytar.node"(exports, module) {
19
+ "use strict";
20
+ init_keytar();
21
+ try {
22
+ module.exports = __require(keytar_default);
23
+ } catch {
24
+ }
25
+ }
26
+ });
27
+
28
+ // ../../node_modules/keytar/lib/keytar.js
29
+ var require_keytar2 = __commonJS({
30
+ "../../node_modules/keytar/lib/keytar.js"(exports, module) {
31
+ var keytar = require_keytar();
32
+ function checkRequired(val, name) {
33
+ if (!val || val.length <= 0) {
34
+ throw new Error(name + " is required.");
35
+ }
36
+ }
37
+ module.exports = {
38
+ getPassword: function(service, account) {
39
+ checkRequired(service, "Service");
40
+ checkRequired(account, "Account");
41
+ return keytar.getPassword(service, account);
42
+ },
43
+ setPassword: function(service, account, password) {
44
+ checkRequired(service, "Service");
45
+ checkRequired(account, "Account");
46
+ checkRequired(password, "Password");
47
+ return keytar.setPassword(service, account, password);
48
+ },
49
+ deletePassword: function(service, account) {
50
+ checkRequired(service, "Service");
51
+ checkRequired(account, "Account");
52
+ return keytar.deletePassword(service, account);
53
+ },
54
+ findPassword: function(service) {
55
+ checkRequired(service, "Service");
56
+ return keytar.findPassword(service);
57
+ },
58
+ findCredentials: function(service) {
59
+ checkRequired(service, "Service");
60
+ return keytar.findCredentials(service);
61
+ }
62
+ };
63
+ }
64
+ });
65
+ export default require_keytar2();
Binary file
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@sudoji/cli",
3
+ "version": "0.1.0",
4
+ "description": "Sudoji installable IT agent CLI — diagnose, approve, fix on your Linux server.",
5
+ "type": "module",
6
+ "bin": {
7
+ "sudoji": "./dist/index.js"
8
+ },
9
+ "files": ["dist"],
10
+ "scripts": {
11
+ "build": "tsup && chmod +x dist/index.js",
12
+ "test": "vitest run",
13
+ "typecheck": "tsc --noEmit"
14
+ },
15
+ "dependencies": {
16
+ "@sudoji/core": "*",
17
+ "commander": "^12.0.0"
18
+ },
19
+ "devDependencies": {
20
+ "tsup": "^8.0.0",
21
+ "typescript": "^5.4.0",
22
+ "vitest": "^1.6.0"
23
+ },
24
+ "optionalDependencies": {
25
+ "keytar": "^7.9.0"
26
+ },
27
+ "engines": {
28
+ "node": ">=18"
29
+ }
30
+ }