From db60a463c715e6b069864caf02b3ae0045a0c304 Mon Sep 17 00:00:00 2001 From: stack72 Date: Wed, 6 May 2026 20:02:15 +0100 Subject: [PATCH 1/2] feat(cli): add swamp issue get command to fetch issue details (swamp-club#118) Adds a lightweight CLI command to fetch and display existing Lab issue details without creating a full lifecycle model instance. Supports both log and json output modes. Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/skills/swamp-issue/SKILL.md | 32 ++++--- .../swamp-issue/references/output_shapes.md | 16 ++++ src/cli/commands/issue.ts | 4 +- src/cli/commands/issue_get.ts | 71 +++++++++++++++ src/cli/commands/issue_get_test.ts | 29 +++++++ src/infrastructure/http/swamp_club_client.ts | 53 ++++++++++++ src/libswamp/issues/get.ts | 77 +++++++++++++++++ src/libswamp/issues/get_test.ts | 86 +++++++++++++++++++ src/libswamp/mod.ts | 7 ++ src/presentation/renderers/issue_get.ts | 85 ++++++++++++++++++ 10 files changed, 447 insertions(+), 13 deletions(-) create mode 100644 src/cli/commands/issue_get.ts create mode 100644 src/cli/commands/issue_get_test.ts create mode 100644 src/libswamp/issues/get.ts create mode 100644 src/libswamp/issues/get_test.ts create mode 100644 src/presentation/renderers/issue_get.ts diff --git a/.claude/skills/swamp-issue/SKILL.md b/.claude/skills/swamp-issue/SKILL.md index 405276df..67f05eb3 100644 --- a/.claude/skills/swamp-issue/SKILL.md +++ b/.claude/skills/swamp-issue/SKILL.md @@ -1,14 +1,19 @@ --- name: swamp-issue -description: Submit issues to the swamp Lab or route them to the publisher's repository — file bug reports, feature requests, and security vulnerability reports against swamp itself or against a specific extension, and post follow-up ripples (comments) on existing Lab issues. Use when the user wants to report a bug, request a feature, disclose a vulnerability, comment on an existing issue, or provide feedback about swamp. Triggers on "bug report", "feature request", "security report", "vulnerability", "report bug", "request feature", "file bug", "submit bug", "swamp bug", "swamp feature", "feedback", "report issue", "file issue", "report against extension", "extension bug", "ripple", "comment on issue", "reply to issue", "follow up on issue", "add comment to issue". +description: Fetch issue details and submit issues to the swamp Lab or route them to the publisher's repository — fetch issue details, file bug reports, feature requests, and security vulnerability reports against swamp itself or against a specific extension, and post follow-up ripples (comments) on existing Lab issues. Use when the user wants to view an issue, report a bug, request a feature, disclose a vulnerability, comment on an existing issue, or provide feedback about swamp. Triggers on "bug report", "feature request", "security report", "vulnerability", "report bug", "request feature", "file bug", "submit bug", "swamp bug", "swamp feature", "feedback", "report issue", "file issue", "report against extension", "extension bug", "ripple", "comment on issue", "reply to issue", "follow up on issue", "add comment to issue", "get issue", "view issue", "fetch issue", "issue details", "show issue". --- -# Swamp Issue Submission Skill +# Swamp Issue Skill -Submit bug reports, feature requests, and security vulnerability disclosures -through the swamp CLI. When logged in (`swamp auth login`), issues are submitted -directly to the swamp.club Lab. When not logged in, the user is prompted to log -in or send via email. The `--email` flag skips straight to a pre-filled email. +Fetch issue details, submit bug reports, feature requests, and security +vulnerability disclosures through the swamp CLI. When logged in +(`swamp auth login`), issues are submitted directly to the swamp.club Lab. When +not logged in, the user is prompted to log in or send via email. The `--email` +flag skips straight to a pre-filled email. + +To view an existing issue, use `swamp issue get ` — this fetches and +displays the issue's title, type, status, author, body, assignees, and comment +count. With `--extension `, reports are routed to the extension's publisher instead — either as a tagged swamp.club Lab issue (for `@swamp/*` extensions) or @@ -28,16 +33,19 @@ a template) and non-interactive mode with `--title` and `--body` flags. `ripple` takes a positional issue number and either opens the editor or accepts `--body` directly. -| Command | Template sections | -| ----------------------------- | ------------------------------------------------------------- | -| `swamp issue bug` | Title, description, steps to reproduce, environment | -| `swamp issue feature` | Title, problem statement, proposed solution, alternatives | -| `swamp issue security` | Title, description, reproduction, affected components, impact | -| `swamp issue ripple ` | Free-form markdown body (no title) | +| Command | Purpose | +| ----------------------------- | --------------------------------------------------------------------------------------------- | +| `swamp issue get ` | Fetch and display issue details (title, type, status, author, body, assignees, comment count) | +| `swamp issue bug` | Title, description, steps to reproduce, environment | +| `swamp issue feature` | Title, problem statement, proposed solution, alternatives | +| `swamp issue security` | Title, description, reproduction, affected components, impact | +| `swamp issue ripple ` | Free-form markdown body (no title) | **Basic non-interactive examples:** ```bash +swamp issue get 42 +swamp issue get 42 --json swamp issue bug --title "CLI crashes on empty input" --body "When running..." --json swamp issue feature --title "Add dark mode" --body "I'd like..." --json swamp issue security --title "..." --body "..." --json diff --git a/.claude/skills/swamp-issue/references/output_shapes.md b/.claude/skills/swamp-issue/references/output_shapes.md index 50db6af0..7810eefd 100644 --- a/.claude/skills/swamp-issue/references/output_shapes.md +++ b/.claude/skills/swamp-issue/references/output_shapes.md @@ -3,6 +3,22 @@ JSON output shapes for `swamp issue *` commands invoked with `--json`. Use these to assert on results programmatically. +## Issue Get (`swamp issue get --json`) + +```json +{ + "number": 42, + "title": "Add swamp issue get CLI command", + "type": "feature", + "status": "open", + "author": "stack72", + "body": "## Problem\n\nThe swamp CLI can submit issues...", + "assignees": ["alice", "bob"], + "commentCount": 3, + "serverUrl": "https://swamp.club" +} +``` + ## Plain Submission (no `--extension`) **Lab submission** (user logged in): diff --git a/src/cli/commands/issue.ts b/src/cli/commands/issue.ts index 0c8a3118..71eaaf4c 100644 --- a/src/cli/commands/issue.ts +++ b/src/cli/commands/issue.ts @@ -20,17 +20,19 @@ import { Command } from "@cliffy/command"; import { issueBugCommand } from "./issue_bug.ts"; import { issueFeatureCommand } from "./issue_feature.ts"; +import { issueGetCommand } from "./issue_get.ts"; import { issueRippleCommand } from "./issue_ripple.ts"; import { issueSecurityCommand } from "./issue_security.ts"; export const issueCommand = new Command() .name("issue") .description( - "Submit bug reports, feature requests, security reports, and ripples", + "Fetch issue details, submit bug reports, feature requests, security reports, and ripples", ) .action(function () { this.showHelp(); }) + .command("get", issueGetCommand) .command("bug", issueBugCommand) .command("feature", issueFeatureCommand) .command("security", issueSecurityCommand) diff --git a/src/cli/commands/issue_get.ts b/src/cli/commands/issue_get.ts new file mode 100644 index 00000000..e5b6f01c --- /dev/null +++ b/src/cli/commands/issue_get.ts @@ -0,0 +1,71 @@ +// Swamp, an Automation Framework +// Copyright (C) 2026 System Initiative, Inc. +// +// This file is part of Swamp. +// +// Swamp is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License version 3 +// as published by the Free Software Foundation, with the Swamp +// Extension and Definition Exception (found in the "COPYING-EXCEPTION" +// file). +// +// Swamp is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with Swamp. If not, see . + +import { Command } from "@cliffy/command"; +import { createContext, type GlobalOptions } from "../context.ts"; +import { + consumeStream, + createLibSwampContext, + issueGet, + type IssueGetDeps, +} from "../../libswamp/mod.ts"; +import { createIssueGetRenderer } from "../../presentation/renderers/issue_get.ts"; +import { AuthRepository } from "../../infrastructure/persistence/auth_repository.ts"; +import { SwampClubClient } from "../../infrastructure/http/swamp_club_client.ts"; +import { UserError } from "../../domain/errors.ts"; + +// deno-lint-ignore no-explicit-any +type AnyOptions = any; + +export const issueGetCommand = new Command() + .name("get") + .description("Fetch and display details of a swamp-club Lab issue") + .example("View issue details", "swamp issue get 42") + .example("Get issue as JSON", "swamp issue get 42 --json") + .arguments("") + .action(async function (options: AnyOptions, issueNumber: number) { + const ctx = createContext(options as GlobalOptions, ["issue", "get"]); + + if (!Number.isInteger(issueNumber) || issueNumber <= 0) { + throw new UserError("Issue number must be a positive integer."); + } + + const credentials = await new AuthRepository().load(); + if (!credentials) { + throw new UserError( + 'Not logged in. Run "swamp auth login" first.', + ); + } + + const client = new SwampClubClient(credentials.serverUrl); + const deps: IssueGetDeps = { + fetchIssue: async (num) => { + const issue = await client.fetchIssue(credentials.apiKey, num); + return { ...issue, serverUrl: credentials.serverUrl }; + }, + }; + + const libCtx = createLibSwampContext({ logger: ctx.logger }); + const renderer = createIssueGetRenderer(ctx.outputMode); + + await consumeStream( + issueGet(libCtx, deps, { issueNumber }), + renderer.handlers(), + ); + }); diff --git a/src/cli/commands/issue_get_test.ts b/src/cli/commands/issue_get_test.ts new file mode 100644 index 00000000..a276e651 --- /dev/null +++ b/src/cli/commands/issue_get_test.ts @@ -0,0 +1,29 @@ +// Swamp, an Automation Framework +// Copyright (C) 2026 System Initiative, Inc. +// +// This file is part of Swamp. +// +// Swamp is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License version 3 +// as published by the Free Software Foundation, with the Swamp +// Extension and Definition Exception (found in the "COPYING-EXCEPTION" +// file). +// +// Swamp is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with Swamp. If not, see . + +import { assertEquals } from "@std/assert"; +import { issueGetCommand } from "./issue_get.ts"; + +Deno.test("issueGetCommand: has correct name and description", () => { + assertEquals(issueGetCommand.getName(), "get"); + assertEquals( + issueGetCommand.getDescription(), + "Fetch and display details of a swamp-club Lab issue", + ); +}); diff --git a/src/infrastructure/http/swamp_club_client.ts b/src/infrastructure/http/swamp_club_client.ts index b3dcdb5c..03c5a0ed 100644 --- a/src/infrastructure/http/swamp_club_client.ts +++ b/src/infrastructure/http/swamp_club_client.ts @@ -65,6 +65,18 @@ export function getCollectives( return whoami.organizations.map((org) => org.slug); } +/** Response from fetching a Lab issue by number. */ +export interface FetchIssueResponse { + number: number; + title: string; + type: string; + status: string; + author: string; + body: string; + assignees: string[]; + commentCount: number; +} + /** * HTTP client for swamp-club API interactions. * Used by auth commands to sign in, create API keys, and verify identity. @@ -252,6 +264,47 @@ export class SwampClubClient { return { id: data.id }; } + /** + * Fetch an existing Lab issue by number. + * Authenticates using the x-api-key header. + */ + async fetchIssue( + apiKey: string, + issueNumber: number, + ): Promise { + const res = await this.fetch(`/api/v1/lab/issues/${issueNumber}`, { + method: "GET", + headers: { "x-api-key": apiKey }, + }); + + if (!res.ok) { + const text = await res.text(); + if (res.status === 404) { + throw new UserError(`Issue #${issueNumber} not found.`); + } + throw new UserError( + `Failed to fetch issue #${issueNumber} (HTTP ${res.status}): ${text}`, + ); + } + + const data = await res.json(); + const issue = data.issue; + return { + number: issue.number, + title: issue.title ?? "", + type: issue.type ?? "feature", + status: issue.status ?? "open", + author: issue.authorUsername ?? "unknown", + body: issue.body ?? "", + assignees: (issue.assignees ?? []) + .filter( + (a: Record) => typeof a.username === "string", + ) + .map((a: Record) => a.username), + commentCount: (issue.comments ?? []).length, + }; + } + private async fetch( path: string, init: RequestInit, diff --git a/src/libswamp/issues/get.ts b/src/libswamp/issues/get.ts new file mode 100644 index 00000000..42c7dcfb --- /dev/null +++ b/src/libswamp/issues/get.ts @@ -0,0 +1,77 @@ +// Swamp, an Automation Framework +// Copyright (C) 2026 System Initiative, Inc. +// +// This file is part of Swamp. +// +// Swamp is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License version 3 +// as published by the Free Software Foundation, with the Swamp +// Extension and Definition Exception (found in the "COPYING-EXCEPTION" +// file). +// +// Swamp is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with Swamp. If not, see . + +import type { LibSwampContext } from "../context.ts"; +import type { SwampError } from "../errors.ts"; +import { withGeneratorSpan } from "../../infrastructure/tracing/mod.ts"; + +export interface IssueGetData { + number: number; + title: string; + type: string; + status: string; + author: string; + body: string; + assignees: string[]; + commentCount: number; + serverUrl: string; +} + +export type IssueGetEvent = + | { kind: "completed"; data: IssueGetData } + | { kind: "error"; error: SwampError }; + +export interface IssueGetInput { + issueNumber: number; +} + +export interface IssueGetDeps { + fetchIssue: (issueNumber: number) => Promise<{ + number: number; + title: string; + type: string; + status: string; + author: string; + body: string; + assignees: string[]; + commentCount: number; + serverUrl: string; + }>; +} + +export async function* issueGet( + ctx: LibSwampContext, + deps: IssueGetDeps, + input: IssueGetInput, +): AsyncIterable { + yield* withGeneratorSpan( + "swamp.issue.get", + {}, + (async function* () { + ctx.logger.debug`Fetching issue #${input.issueNumber}`; + + const result = await deps.fetchIssue(input.issueNumber); + + yield { + kind: "completed", + data: result, + }; + })(), + ); +} diff --git a/src/libswamp/issues/get_test.ts b/src/libswamp/issues/get_test.ts new file mode 100644 index 00000000..a4e31273 --- /dev/null +++ b/src/libswamp/issues/get_test.ts @@ -0,0 +1,86 @@ +// Swamp, an Automation Framework +// Copyright (C) 2026 System Initiative, Inc. +// +// This file is part of Swamp. +// +// Swamp is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License version 3 +// as published by the Free Software Foundation, with the Swamp +// Extension and Definition Exception (found in the "COPYING-EXCEPTION" +// file). +// +// Swamp is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with Swamp. If not, see . + +import { assertEquals } from "@std/assert"; +import { collect } from "../testing.ts"; +import { createLibSwampContext } from "../context.ts"; +import { issueGet, type IssueGetDeps, type IssueGetEvent } from "./get.ts"; + +function makeDeps(overrides: Partial = {}): IssueGetDeps { + return { + fetchIssue: () => + Promise.resolve({ + number: 42, + title: "Example issue", + type: "bug", + status: "open", + author: "testuser", + body: "Something is broken.", + assignees: ["alice"], + commentCount: 3, + serverUrl: "https://swamp-club.com", + }), + ...overrides, + }; +} + +Deno.test("issueGet: yields completed with fetched issue data", async () => { + const events = await collect( + issueGet(createLibSwampContext(), makeDeps(), { issueNumber: 42 }), + ); + + assertEquals(events.length, 1); + const completed = events[0] as Extract; + assertEquals(completed.kind, "completed"); + assertEquals(completed.data.number, 42); + assertEquals(completed.data.title, "Example issue"); + assertEquals(completed.data.type, "bug"); + assertEquals(completed.data.status, "open"); + assertEquals(completed.data.author, "testuser"); + assertEquals(completed.data.body, "Something is broken."); + assertEquals(completed.data.assignees, ["alice"]); + assertEquals(completed.data.commentCount, 3); + assertEquals(completed.data.serverUrl, "https://swamp-club.com"); +}); + +Deno.test("issueGet: passes issueNumber to fetchIssue dep", async () => { + let captured: number | undefined; + const deps = makeDeps({ + fetchIssue: (issueNumber) => { + captured = issueNumber; + return Promise.resolve({ + number: issueNumber, + title: "Captured", + type: "feature", + status: "triaged", + author: "bob", + body: "A feature.", + assignees: [], + commentCount: 0, + serverUrl: "https://swamp-club.com", + }); + }, + }); + + await collect( + issueGet(createLibSwampContext(), deps, { issueNumber: 99 }), + ); + + assertEquals(captured, 99); +}); diff --git a/src/libswamp/mod.ts b/src/libswamp/mod.ts index ddd4091c..58f86a32 100644 --- a/src/libswamp/mod.ts +++ b/src/libswamp/mod.ts @@ -946,6 +946,13 @@ export { type IssueCommentInput, MAX_RIPPLE_LENGTH, } from "./issues/comment.ts"; +export { + issueGet, + type IssueGetData, + type IssueGetDeps, + type IssueGetEvent, + type IssueGetInput, +} from "./issues/get.ts"; // Audit operations export { diff --git a/src/presentation/renderers/issue_get.ts b/src/presentation/renderers/issue_get.ts new file mode 100644 index 00000000..c475ffbd --- /dev/null +++ b/src/presentation/renderers/issue_get.ts @@ -0,0 +1,85 @@ +// Swamp, an Automation Framework +// Copyright (C) 2026 System Initiative, Inc. +// +// This file is part of Swamp. +// +// Swamp is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License version 3 +// as published by the Free Software Foundation, with the Swamp +// Extension and Definition Exception (found in the "COPYING-EXCEPTION" +// file). +// +// Swamp is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with Swamp. If not, see . + +import type { EventHandlers, IssueGetEvent } from "../../libswamp/mod.ts"; +import type { Renderer } from "../renderer.ts"; +import type { OutputMode } from "../output/output.ts"; +import { getSwampLogger } from "../../infrastructure/logging/logger.ts"; +import { UserError } from "../../domain/errors.ts"; + +class LogIssueGetRenderer implements Renderer { + handlers(): EventHandlers { + const logger = getSwampLogger(["issue", "get"]); + return { + completed: (e) => { + const d = e.data; + logger.info("#{number}: {title}", { + number: d.number, + title: d.title, + }); + logger.info("Type: {type} Status: {status} Author: {author}", { + type: d.type, + status: d.status, + author: d.author, + }); + if (d.assignees.length > 0) { + logger.info("Assignees: {assignees}", { + assignees: d.assignees.join(", "), + }); + } + logger.info("Comments: {count}", { count: d.commentCount }); + if (d.body.length > 0) { + logger.info(""); + logger.info("{body}", { body: d.body }); + } + logger.info(""); + logger.info("View at: {url}", { + url: `${d.serverUrl}/lab/${d.number}`, + }); + }, + error: (e) => { + throw new UserError(e.error.message); + }, + }; + } +} + +class JsonIssueGetRenderer implements Renderer { + handlers(): EventHandlers { + return { + completed: (e) => { + console.log(JSON.stringify(e.data, null, 2)); + }, + error: (e) => { + throw new UserError(e.error.message); + }, + }; + } +} + +export function createIssueGetRenderer( + mode: OutputMode, +): Renderer { + switch (mode) { + case "json": + return new JsonIssueGetRenderer(); + case "log": + return new LogIssueGetRenderer(); + } +} From bc6d1b893cc2cea3c8f397ebdb8e8f98f541dac1 Mon Sep 17 00:00:00 2001 From: stack72 Date: Wed, 6 May 2026 20:47:19 +0100 Subject: [PATCH 2/2] fix(cli): address CLI UX review feedback for issue get renderer Switch log-mode renderer from logger.info() to writeOutput(), add color formatting with bold/cyan/dim, and render issue body as markdown via renderMarkdownToTerminal(). Deduplicate IssueGetDeps return type. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/libswamp/issues/get.ts | 12 +-------- src/presentation/renderers/issue_get.ts | 36 +++++++++++-------------- 2 files changed, 16 insertions(+), 32 deletions(-) diff --git a/src/libswamp/issues/get.ts b/src/libswamp/issues/get.ts index 42c7dcfb..89a2313f 100644 --- a/src/libswamp/issues/get.ts +++ b/src/libswamp/issues/get.ts @@ -42,17 +42,7 @@ export interface IssueGetInput { } export interface IssueGetDeps { - fetchIssue: (issueNumber: number) => Promise<{ - number: number; - title: string; - type: string; - status: string; - author: string; - body: string; - assignees: string[]; - commentCount: number; - serverUrl: string; - }>; + fetchIssue: (issueNumber: number) => Promise; } export async function* issueGet( diff --git a/src/presentation/renderers/issue_get.ts b/src/presentation/renderers/issue_get.ts index c475ffbd..1141ff12 100644 --- a/src/presentation/renderers/issue_get.ts +++ b/src/presentation/renderers/issue_get.ts @@ -20,38 +20,32 @@ import type { EventHandlers, IssueGetEvent } from "../../libswamp/mod.ts"; import type { Renderer } from "../renderer.ts"; import type { OutputMode } from "../output/output.ts"; -import { getSwampLogger } from "../../infrastructure/logging/logger.ts"; +import { writeOutput } from "../../infrastructure/logging/logger.ts"; import { UserError } from "../../domain/errors.ts"; +import { bold, cyan, dim } from "@std/fmt/colors"; +import { renderMarkdownToTerminal } from "../markdown_renderer.ts"; class LogIssueGetRenderer implements Renderer { handlers(): EventHandlers { - const logger = getSwampLogger(["issue", "get"]); return { completed: (e) => { const d = e.data; - logger.info("#{number}: {title}", { - number: d.number, - title: d.title, - }); - logger.info("Type: {type} Status: {status} Author: {author}", { - type: d.type, - status: d.status, - author: d.author, - }); + writeOutput(`${bold(cyan(`#${d.number}`))} ${d.title}`); + writeOutput( + `${bold("Type:")} ${d.type} ${bold("Status:")} ${d.status} ${ + bold("Author:") + } ${d.author}`, + ); if (d.assignees.length > 0) { - logger.info("Assignees: {assignees}", { - assignees: d.assignees.join(", "), - }); + writeOutput(`${bold("Assignees:")} ${d.assignees.join(", ")}`); } - logger.info("Comments: {count}", { count: d.commentCount }); + writeOutput(`${bold("Comments:")} ${d.commentCount}`); if (d.body.length > 0) { - logger.info(""); - logger.info("{body}", { body: d.body }); + writeOutput(""); + writeOutput(renderMarkdownToTerminal(d.body)); } - logger.info(""); - logger.info("View at: {url}", { - url: `${d.serverUrl}/lab/${d.number}`, - }); + writeOutput(""); + writeOutput(dim(`View at: ${d.serverUrl}/lab/${d.number}`)); }, error: (e) => { throw new UserError(e.error.message);