@delega-dev/cli 1.0.3 → 1.0.5

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.
@@ -10,9 +10,9 @@ jobs:
10
10
  build:
11
11
  runs-on: ubuntu-latest
12
12
  steps:
13
- - uses: actions/checkout@v4
13
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
14
14
 
15
- - uses: actions/setup-node@v4
15
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
16
16
  with:
17
17
  node-version: 22
18
18
  cache: npm
@@ -12,11 +12,11 @@ jobs:
12
12
  publish:
13
13
  runs-on: ubuntu-latest
14
14
  steps:
15
- - uses: actions/checkout@v4
15
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
16
16
  with:
17
17
  fetch-depth: 0
18
18
 
19
- - uses: actions/setup-node@v4
19
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
20
20
  with:
21
21
  node-version: 22
22
22
  cache: npm
@@ -45,7 +45,6 @@ jobs:
45
45
  NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
46
46
 
47
47
  - name: Generate changelog
48
- id: changelog
49
48
  run: |
50
49
  PREV_TAG=$(git tag --sort=-v:refname | sed -n '2p')
51
50
  if [ -n "$PREV_TAG" ]; then
@@ -57,7 +56,6 @@ jobs:
57
56
  echo "" >> /tmp/changelog.md
58
57
  git log --pretty=format:"- %s (%h)" --no-merges >> /tmp/changelog.md
59
58
  fi
60
- cat /tmp/changelog.md
61
59
 
62
60
  - name: Create GitHub Release
63
61
  env:
package/README.md CHANGED
@@ -104,6 +104,12 @@ Config is stored in `~/.delega/config.json`:
104
104
 
105
105
  Environment variables take precedence over the config file.
106
106
 
107
+ ## Security Notes
108
+
109
+ - `delega login` now hides API key input instead of echoing it back to the terminal.
110
+ - `~/.delega/config.json` is written with owner-only permissions (`0600`), and the config directory is locked to `0700`.
111
+ - Remote API URLs must use `https://`; plain `http://` is only accepted for `localhost` / `127.0.0.1`.
112
+
107
113
  ## License
108
114
 
109
115
  MIT
package/dist/api.js CHANGED
@@ -5,7 +5,16 @@ export async function apiCall(method, path, body) {
5
5
  console.error("Not authenticated. Run: delega login");
6
6
  process.exit(1);
7
7
  }
8
- const url = getApiUrl() + path;
8
+ let apiBase;
9
+ try {
10
+ apiBase = getApiUrl();
11
+ }
12
+ catch (err) {
13
+ const msg = err instanceof Error ? err.message : String(err);
14
+ console.error(`Configuration error: ${msg}`);
15
+ process.exit(1);
16
+ }
17
+ const url = apiBase + path;
9
18
  const headers = {
10
19
  "X-Agent-Key": apiKey,
11
20
  "Content-Type": "application/json",
@@ -1,15 +1,26 @@
1
1
  import { Command } from "commander";
2
2
  import node_readline from "node:readline";
3
- import { saveConfig, loadConfig } from "../config.js";
3
+ import { saveConfig, loadConfig, normalizeApiUrl } from "../config.js";
4
4
  import { printBanner } from "../ui.js";
5
- async function prompt(question) {
5
+ async function promptSecret(question) {
6
+ const mutedOutput = {
7
+ muted: false,
8
+ write(chunk) {
9
+ if (!this.muted || chunk.includes(question)) {
10
+ process.stdout.write(chunk);
11
+ }
12
+ },
13
+ };
6
14
  const rl = node_readline.createInterface({
7
15
  input: process.stdin,
8
- output: process.stdout,
16
+ output: mutedOutput,
17
+ terminal: true,
9
18
  });
19
+ mutedOutput.muted = true;
10
20
  return new Promise((resolve) => {
11
21
  rl.question(question, (answer) => {
12
22
  rl.close();
23
+ process.stdout.write("\n");
13
24
  resolve(answer.trim());
14
25
  });
15
26
  });
@@ -18,7 +29,7 @@ export const loginCommand = new Command("login")
18
29
  .description("Authenticate with the Delega API")
19
30
  .action(async () => {
20
31
  printBanner();
21
- const key = await prompt("Enter your API key (starts with dlg_): ");
32
+ const key = await promptSecret("Enter your API key (starts with dlg_): ");
22
33
  if (!key) {
23
34
  console.error("No key provided.");
24
35
  process.exit(1);
@@ -29,10 +40,18 @@ export const loginCommand = new Command("login")
29
40
  }
30
41
  // Validate by calling the API
31
42
  const config = loadConfig();
32
- const apiUrl = config.api_url || process.env.DELEGA_API_URL || "https://api.delega.dev";
43
+ let apiUrl;
44
+ try {
45
+ apiUrl = normalizeApiUrl(config.api_url || process.env.DELEGA_API_URL || "https://api.delega.dev");
46
+ }
47
+ catch (err) {
48
+ const msg = err instanceof Error ? err.message : String(err);
49
+ console.error(`Configuration error: ${msg}`);
50
+ process.exit(1);
51
+ }
33
52
  let res;
34
53
  try {
35
- res = await fetch(`${apiUrl}/v1/agents`, {
54
+ res = await fetch(`${apiUrl}/v1/agent/me`, {
36
55
  headers: {
37
56
  "X-Agent-Key": key,
38
57
  "Content-Type": "application/json",
@@ -51,11 +70,8 @@ export const loginCommand = new Command("login")
51
70
  let agentName = "agent";
52
71
  try {
53
72
  const data = (await res.json());
54
- if (Array.isArray(data) && data.length > 0) {
55
- agentName = data[0].display_name || data[0].name;
56
- }
57
- else if (!Array.isArray(data) && data.name) {
58
- agentName = data.display_name || data.name;
73
+ if (data.agent?.name) {
74
+ agentName = data.agent.display_name || data.agent.name;
59
75
  }
60
76
  }
61
77
  catch {
package/dist/config.d.ts CHANGED
@@ -4,5 +4,6 @@ export interface DelegaConfig {
4
4
  }
5
5
  export declare function loadConfig(): DelegaConfig;
6
6
  export declare function saveConfig(config: DelegaConfig): void;
7
+ export declare function normalizeApiUrl(rawUrl: string): string;
7
8
  export declare function getApiKey(): string | undefined;
8
9
  export declare function getApiUrl(): string;
package/dist/config.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import node_fs from "node:fs";
2
2
  import node_path from "node:path";
3
3
  import node_os from "node:os";
4
+ const LOCAL_API_HOSTS = new Set(["localhost", "127.0.0.1"]);
4
5
  function getConfigDir() {
5
6
  return node_path.join(node_os.homedir(), ".delega");
6
7
  }
@@ -23,15 +24,30 @@ export function loadConfig() {
23
24
  export function saveConfig(config) {
24
25
  const configDir = getConfigDir();
25
26
  if (!node_fs.existsSync(configDir)) {
26
- node_fs.mkdirSync(configDir, { recursive: true });
27
+ node_fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
27
28
  }
28
- node_fs.writeFileSync(getConfigPath(), JSON.stringify(config, null, 2) + "\n", "utf-8");
29
+ node_fs.chmodSync(configDir, 0o700);
30
+ node_fs.writeFileSync(getConfigPath(), JSON.stringify(config, null, 2) + "\n", { encoding: "utf-8", mode: 0o600 });
31
+ node_fs.chmodSync(getConfigPath(), 0o600);
32
+ }
33
+ export function normalizeApiUrl(rawUrl) {
34
+ let parsed;
35
+ try {
36
+ parsed = new URL(rawUrl);
37
+ }
38
+ catch {
39
+ throw new Error("Invalid Delega API URL");
40
+ }
41
+ if (parsed.protocol !== "https:" && !LOCAL_API_HOSTS.has(parsed.hostname)) {
42
+ throw new Error("Delega API URL must use HTTPS unless it points to localhost");
43
+ }
44
+ return rawUrl.replace(/\/+$/, "");
29
45
  }
30
46
  export function getApiKey() {
31
47
  return process.env.DELEGA_API_KEY || loadConfig().api_key;
32
48
  }
33
49
  export function getApiUrl() {
34
- return (process.env.DELEGA_API_URL ||
50
+ return normalizeApiUrl(process.env.DELEGA_API_URL ||
35
51
  loadConfig().api_url ||
36
52
  "https://api.delega.dev");
37
53
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delega-dev/cli",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "CLI for Delega task API",
5
5
  "type": "module",
6
6
  "bin": {
@@ -35,7 +35,8 @@
35
35
  "node": ">=18.0.0"
36
36
  },
37
37
  "publishConfig": {
38
- "access": "public"
38
+ "access": "public",
39
+ "registry": "https://registry.npmjs.org/"
39
40
  },
40
41
  "homepage": "https://delega.dev",
41
42
  "bugs": {
package/src/api.ts CHANGED
@@ -16,7 +16,16 @@ export async function apiCall<T = unknown>(
16
16
  process.exit(1);
17
17
  }
18
18
 
19
- const url = getApiUrl() + path;
19
+ let apiBase: string;
20
+ try {
21
+ apiBase = getApiUrl();
22
+ } catch (err) {
23
+ const msg = err instanceof Error ? err.message : String(err);
24
+ console.error(`Configuration error: ${msg}`);
25
+ process.exit(1);
26
+ }
27
+
28
+ const url = apiBase + path;
20
29
 
21
30
  const headers: Record<string, string> = {
22
31
  "X-Agent-Key": apiKey,
@@ -1,6 +1,6 @@
1
1
  import { Command } from "commander";
2
2
  import node_readline from "node:readline";
3
- import { saveConfig, loadConfig } from "../config.js";
3
+ import { saveConfig, loadConfig, normalizeApiUrl } from "../config.js";
4
4
  import { printBanner } from "../ui.js";
5
5
 
6
6
  interface Agent {
@@ -9,14 +9,28 @@ interface Agent {
9
9
  display_name?: string;
10
10
  }
11
11
 
12
- async function prompt(question: string): Promise<string> {
12
+ async function promptSecret(question: string): Promise<string> {
13
+ const mutedOutput = {
14
+ muted: false,
15
+ write(chunk: string) {
16
+ if (!this.muted || chunk.includes(question)) {
17
+ process.stdout.write(chunk);
18
+ }
19
+ },
20
+ };
21
+
13
22
  const rl = node_readline.createInterface({
14
23
  input: process.stdin,
15
- output: process.stdout,
24
+ output: mutedOutput as unknown as NodeJS.WritableStream,
25
+ terminal: true,
16
26
  });
27
+
28
+ mutedOutput.muted = true;
29
+
17
30
  return new Promise((resolve) => {
18
31
  rl.question(question, (answer) => {
19
32
  rl.close();
33
+ process.stdout.write("\n");
20
34
  resolve(answer.trim());
21
35
  });
22
36
  });
@@ -27,7 +41,7 @@ export const loginCommand = new Command("login")
27
41
  .action(async () => {
28
42
  printBanner();
29
43
 
30
- const key = await prompt("Enter your API key (starts with dlg_): ");
44
+ const key = await promptSecret("Enter your API key (starts with dlg_): ");
31
45
 
32
46
  if (!key) {
33
47
  console.error("No key provided.");
@@ -41,11 +55,20 @@ export const loginCommand = new Command("login")
41
55
 
42
56
  // Validate by calling the API
43
57
  const config = loadConfig();
44
- const apiUrl = config.api_url || process.env.DELEGA_API_URL || "https://api.delega.dev";
58
+ let apiUrl: string;
59
+ try {
60
+ apiUrl = normalizeApiUrl(
61
+ config.api_url || process.env.DELEGA_API_URL || "https://api.delega.dev",
62
+ );
63
+ } catch (err) {
64
+ const msg = err instanceof Error ? err.message : String(err);
65
+ console.error(`Configuration error: ${msg}`);
66
+ process.exit(1);
67
+ }
45
68
 
46
69
  let res: Response;
47
70
  try {
48
- res = await fetch(`${apiUrl}/v1/agents`, {
71
+ res = await fetch(`${apiUrl}/v1/agent/me`, {
49
72
  headers: {
50
73
  "X-Agent-Key": key,
51
74
  "Content-Type": "application/json",
@@ -64,11 +87,9 @@ export const loginCommand = new Command("login")
64
87
 
65
88
  let agentName = "agent";
66
89
  try {
67
- const data = (await res.json()) as Agent | Agent[];
68
- if (Array.isArray(data) && data.length > 0) {
69
- agentName = data[0].display_name || data[0].name;
70
- } else if (!Array.isArray(data) && data.name) {
71
- agentName = data.display_name || data.name;
90
+ const data = (await res.json()) as { agent?: Agent };
91
+ if (data.agent?.name) {
92
+ agentName = data.agent.display_name || data.agent.name;
72
93
  }
73
94
  } catch {
74
95
  // Proceed with default name
package/src/config.ts CHANGED
@@ -7,6 +7,8 @@ export interface DelegaConfig {
7
7
  api_url?: string;
8
8
  }
9
9
 
10
+ const LOCAL_API_HOSTS = new Set(["localhost", "127.0.0.1"]);
11
+
10
12
  function getConfigDir(): string {
11
13
  return node_path.join(node_os.homedir(), ".delega");
12
14
  }
@@ -31,13 +33,30 @@ export function loadConfig(): DelegaConfig {
31
33
  export function saveConfig(config: DelegaConfig): void {
32
34
  const configDir = getConfigDir();
33
35
  if (!node_fs.existsSync(configDir)) {
34
- node_fs.mkdirSync(configDir, { recursive: true });
36
+ node_fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
35
37
  }
38
+ node_fs.chmodSync(configDir, 0o700);
36
39
  node_fs.writeFileSync(
37
40
  getConfigPath(),
38
41
  JSON.stringify(config, null, 2) + "\n",
39
- "utf-8",
42
+ { encoding: "utf-8", mode: 0o600 },
40
43
  );
44
+ node_fs.chmodSync(getConfigPath(), 0o600);
45
+ }
46
+
47
+ export function normalizeApiUrl(rawUrl: string): string {
48
+ let parsed: URL;
49
+ try {
50
+ parsed = new URL(rawUrl);
51
+ } catch {
52
+ throw new Error("Invalid Delega API URL");
53
+ }
54
+
55
+ if (parsed.protocol !== "https:" && !LOCAL_API_HOSTS.has(parsed.hostname)) {
56
+ throw new Error("Delega API URL must use HTTPS unless it points to localhost");
57
+ }
58
+
59
+ return rawUrl.replace(/\/+$/, "");
41
60
  }
42
61
 
43
62
  export function getApiKey(): string | undefined {
@@ -45,7 +64,7 @@ export function getApiKey(): string | undefined {
45
64
  }
46
65
 
47
66
  export function getApiUrl(): string {
48
- return (
67
+ return normalizeApiUrl(
49
68
  process.env.DELEGA_API_URL ||
50
69
  loadConfig().api_url ||
51
70
  "https://api.delega.dev"