@ship-cli/core 0.0.3 → 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.
Files changed (52) hide show
  1. package/README.md +90 -0
  2. package/dist/bin.js +43263 -30230
  3. package/package.json +47 -23
  4. package/.tsbuildinfo/src.tsbuildinfo +0 -1
  5. package/.tsbuildinfo/test.tsbuildinfo +0 -1
  6. package/LICENSE +0 -21
  7. package/src/adapters/driven/auth/AuthServiceLive.ts +0 -125
  8. package/src/adapters/driven/config/ConfigRepositoryLive.ts +0 -366
  9. package/src/adapters/driven/linear/IssueRepositoryLive.ts +0 -528
  10. package/src/adapters/driven/linear/LinearClient.ts +0 -33
  11. package/src/adapters/driven/linear/Mapper.ts +0 -142
  12. package/src/adapters/driven/linear/ProjectRepositoryLive.ts +0 -98
  13. package/src/adapters/driven/linear/TeamRepositoryLive.ts +0 -101
  14. package/src/adapters/driving/cli/commands/block.ts +0 -63
  15. package/src/adapters/driving/cli/commands/blocked.ts +0 -61
  16. package/src/adapters/driving/cli/commands/create.ts +0 -83
  17. package/src/adapters/driving/cli/commands/done.ts +0 -82
  18. package/src/adapters/driving/cli/commands/init.ts +0 -194
  19. package/src/adapters/driving/cli/commands/list.ts +0 -87
  20. package/src/adapters/driving/cli/commands/login.ts +0 -46
  21. package/src/adapters/driving/cli/commands/prime.ts +0 -123
  22. package/src/adapters/driving/cli/commands/project.ts +0 -155
  23. package/src/adapters/driving/cli/commands/ready.ts +0 -73
  24. package/src/adapters/driving/cli/commands/relate.ts +0 -56
  25. package/src/adapters/driving/cli/commands/show.ts +0 -94
  26. package/src/adapters/driving/cli/commands/start.ts +0 -101
  27. package/src/adapters/driving/cli/commands/team.ts +0 -135
  28. package/src/adapters/driving/cli/commands/unblock.ts +0 -63
  29. package/src/adapters/driving/cli/commands/update.ts +0 -125
  30. package/src/adapters/driving/cli/main.ts +0 -76
  31. package/src/bin.ts +0 -12
  32. package/src/domain/Config.ts +0 -42
  33. package/src/domain/Errors.ts +0 -89
  34. package/src/domain/Task.ts +0 -124
  35. package/src/domain/index.ts +0 -3
  36. package/src/infrastructure/Layers.ts +0 -45
  37. package/src/ports/AuthService.ts +0 -19
  38. package/src/ports/ConfigRepository.ts +0 -20
  39. package/src/ports/IssueRepository.ts +0 -75
  40. package/src/ports/PrService.ts +0 -52
  41. package/src/ports/ProjectRepository.ts +0 -19
  42. package/src/ports/TeamRepository.ts +0 -17
  43. package/src/ports/VcsService.ts +0 -87
  44. package/src/ports/index.ts +0 -7
  45. package/test/Dummy.test.ts +0 -7
  46. package/tsconfig.base.json +0 -45
  47. package/tsconfig.json +0 -7
  48. package/tsconfig.src.json +0 -11
  49. package/tsconfig.test.json +0 -10
  50. package/tsconfig.tsbuildinfo +0 -1
  51. package/tsup.config.ts +0 -14
  52. package/vitest.config.ts +0 -12
@@ -1,101 +0,0 @@
1
- import * as Command from "@effect/cli/Command";
2
- import * as Args from "@effect/cli/Args";
3
- import * as Options from "@effect/cli/Options";
4
- import * as Effect from "effect/Effect";
5
- import * as Console from "effect/Console";
6
- import * as Option from "effect/Option";
7
- import { ConfigRepository } from "../../../../ports/ConfigRepository.js";
8
- import { IssueRepository } from "../../../../ports/IssueRepository.js";
9
- import { UpdateTaskInput, type TaskId } from "../../../../domain/Task.js";
10
-
11
- const taskIdArg = Args.text({ name: "task-id" }).pipe(
12
- Args.withDescription("Task identifier (e.g., ENG-123)"),
13
- );
14
-
15
- const jsonOption = Options.boolean("json").pipe(
16
- Options.withDescription("Output as JSON"),
17
- Options.withDefault(false),
18
- );
19
-
20
- export const startCommand = Command.make(
21
- "start",
22
- { taskId: taskIdArg, json: jsonOption },
23
- ({ taskId, json }) =>
24
- Effect.gen(function* () {
25
- const config = yield* ConfigRepository;
26
- const issueRepo = yield* IssueRepository;
27
-
28
- yield* config.load(); // Ensure initialized
29
-
30
- // Get the task
31
- const task = yield* issueRepo
32
- .getTaskByIdentifier(taskId)
33
- .pipe(Effect.catchTag("TaskNotFoundError", () => issueRepo.getTask(taskId as TaskId)));
34
-
35
- // Check if already in progress (Linear's "started" state type)
36
- if (task.state.type === "started") {
37
- if (json) {
38
- yield* Console.log(JSON.stringify({ status: "already_in_progress", task: taskId }));
39
- } else {
40
- yield* Console.log(
41
- `Task ${task.identifier} is already in progress (${task.state.name}).`,
42
- );
43
- }
44
- return;
45
- }
46
-
47
- // Check if blocked
48
- if (task.blockedBy.length > 0) {
49
- if (json) {
50
- yield* Console.log(
51
- JSON.stringify({
52
- status: "blocked",
53
- task: taskId,
54
- blockedBy: task.blockedBy,
55
- }),
56
- );
57
- } else {
58
- yield* Console.log(
59
- `Warning: Task ${task.identifier} is blocked by: ${task.blockedBy.join(", ")}`,
60
- );
61
- yield* Console.log("Consider working on the blocking tasks first.");
62
- }
63
- // Continue anyway - user might want to start despite blockers
64
- }
65
-
66
- // Update status to in_progress
67
- const updateInput = new UpdateTaskInput({
68
- title: Option.none(),
69
- description: Option.none(),
70
- status: Option.some("in_progress"),
71
- priority: Option.none(),
72
- });
73
-
74
- const updatedTask = yield* issueRepo.updateTask(task.id, updateInput);
75
-
76
- // Get branch name
77
- const branchName = yield* issueRepo.getBranchName(task.id);
78
-
79
- if (json) {
80
- yield* Console.log(
81
- JSON.stringify({
82
- status: "started",
83
- task: {
84
- id: updatedTask.id,
85
- identifier: updatedTask.identifier,
86
- title: updatedTask.title,
87
- state: updatedTask.state.name,
88
- branchName,
89
- },
90
- }),
91
- );
92
- } else {
93
- yield* Console.log(`Started: ${updatedTask.identifier} - ${updatedTask.title}`);
94
- yield* Console.log(`Status: ${updatedTask.state.name}`);
95
- yield* Console.log(`\nBranch name: ${branchName}`);
96
- yield* Console.log("\nTo create a jj change with this branch (Phase 2):");
97
- yield* Console.log(` jj new -m "${updatedTask.identifier}: ${updatedTask.title}"`);
98
- yield* Console.log(` jj bookmark create ${branchName}`);
99
- }
100
- }),
101
- );
@@ -1,135 +0,0 @@
1
- import * as Command from "@effect/cli/Command";
2
- import * as Effect from "effect/Effect";
3
- import * as Option from "effect/Option";
4
- import * as clack from "@clack/prompts";
5
- import { ConfigRepository } from "../../../../ports/ConfigRepository.js";
6
- import { AuthService } from "../../../../ports/AuthService.js";
7
- import { TeamRepository } from "../../../../ports/TeamRepository.js";
8
- import { LinearConfig } from "../../../../domain/Config.js";
9
- import type { Team, TeamId } from "../../../../domain/Task.js";
10
-
11
- const CREATE_NEW = "__create_new__" as const;
12
-
13
- export const teamCommand = Command.make("team", {}, () =>
14
- Effect.gen(function* () {
15
- const config = yield* ConfigRepository;
16
- const auth = yield* AuthService;
17
- const teamRepo = yield* TeamRepository;
18
-
19
- clack.intro("ship team");
20
-
21
- // Check authentication
22
- const isAuth = yield* auth.isAuthenticated();
23
- if (!isAuth) {
24
- clack.log.error("Not authenticated. Run 'ship login' first.");
25
- clack.outro("Setup required");
26
- return;
27
- }
28
-
29
- // Show current team
30
- const partial = yield* config.loadPartial();
31
- if (Option.isSome(partial.linear)) {
32
- clack.log.info(`Current team: ${partial.linear.value.teamKey}`);
33
- }
34
-
35
- // Fetch teams
36
- const spinner = clack.spinner();
37
- spinner.start("Fetching teams...");
38
- const teams = yield* teamRepo.getTeams();
39
- spinner.stop("Teams loaded");
40
-
41
- // Select team or create new
42
- const currentTeamId = Option.isSome(partial.linear) ? partial.linear.value.teamId : null;
43
- const teamOptions: Array<{ value: TeamId | typeof CREATE_NEW; label: string; hint?: string }> =
44
- [
45
- ...teams.map((t) =>
46
- currentTeamId === t.id
47
- ? { value: t.id, label: `${t.key} - ${t.name}`, hint: "current" as const }
48
- : { value: t.id, label: `${t.key} - ${t.name}` },
49
- ),
50
- { value: CREATE_NEW, label: "Create new team..." },
51
- ];
52
-
53
- const teamChoice = yield* Effect.tryPromise({
54
- try: () =>
55
- clack.select({
56
- message: "Select a team",
57
- options: teamOptions,
58
- }),
59
- catch: () => new Error("Prompt cancelled"),
60
- });
61
-
62
- if (clack.isCancel(teamChoice)) {
63
- clack.cancel("Cancelled");
64
- return;
65
- }
66
-
67
- let selectedTeam: Team;
68
-
69
- if (teamChoice === CREATE_NEW) {
70
- // Create new team
71
- const teamName = yield* Effect.tryPromise({
72
- try: () =>
73
- clack.text({
74
- message: "Team name",
75
- placeholder: "My Team",
76
- validate: (v) => (!v ? "Name is required" : undefined),
77
- }),
78
- catch: () => new Error("Prompt cancelled"),
79
- });
80
-
81
- if (clack.isCancel(teamName)) {
82
- clack.cancel("Cancelled");
83
- return;
84
- }
85
-
86
- const teamKey = yield* Effect.tryPromise({
87
- try: () =>
88
- clack.text({
89
- message: "Team key (short identifier, e.g. ENG)",
90
- placeholder: "ENG",
91
- validate: (v) => {
92
- if (!v) return "Key is required";
93
- if (!/^[A-Z]{2,5}$/.test(v.toUpperCase())) return "Key must be 2-5 uppercase letters";
94
- },
95
- }),
96
- catch: () => new Error("Prompt cancelled"),
97
- });
98
-
99
- if (clack.isCancel(teamKey)) {
100
- clack.cancel("Cancelled");
101
- return;
102
- }
103
-
104
- const createSpinner = clack.spinner();
105
- createSpinner.start("Creating team...");
106
-
107
- selectedTeam = yield* teamRepo.createTeam({
108
- name: teamName as string,
109
- key: (teamKey as string).toUpperCase(),
110
- });
111
-
112
- createSpinner.stop(`Created team: ${selectedTeam.key}`);
113
- } else {
114
- const found = teams.find((t) => t.id === teamChoice);
115
- if (!found) {
116
- clack.log.error("Selected team not found. Please try again.");
117
- clack.outro("Error");
118
- return;
119
- }
120
- selectedTeam = found;
121
- }
122
-
123
- // Save new team config (clears project since it's team-specific)
124
- const linearConfig = new LinearConfig({
125
- teamId: selectedTeam.id,
126
- teamKey: selectedTeam.key,
127
- projectId: Option.none(),
128
- });
129
-
130
- yield* config.saveLinear(linearConfig);
131
-
132
- clack.log.success(`Switched to team: ${selectedTeam.key} - ${selectedTeam.name}`);
133
- clack.outro("Run 'ship project' to select a project.");
134
- }),
135
- );
@@ -1,63 +0,0 @@
1
- import * as Command from "@effect/cli/Command";
2
- import * as Args from "@effect/cli/Args";
3
- import * as Options from "@effect/cli/Options";
4
- import * as Effect from "effect/Effect";
5
- import * as Console from "effect/Console";
6
- import { ConfigRepository } from "../../../../ports/ConfigRepository.js";
7
- import { IssueRepository } from "../../../../ports/IssueRepository.js";
8
- import type { TaskId } from "../../../../domain/Task.js";
9
-
10
- const blockerArg = Args.text({ name: "blocker" }).pipe(
11
- Args.withDescription("Task that was blocking (e.g., ENG-123)"),
12
- );
13
-
14
- const blockedArg = Args.text({ name: "blocked" }).pipe(
15
- Args.withDescription("Task that was blocked (e.g., ENG-456)"),
16
- );
17
-
18
- const jsonOption = Options.boolean("json").pipe(
19
- Options.withDescription("Output as JSON"),
20
- Options.withDefault(false),
21
- );
22
-
23
- export const unblockCommand = Command.make(
24
- "unblock",
25
- { blocker: blockerArg, blocked: blockedArg, json: jsonOption },
26
- ({ blocker, blocked, json }) =>
27
- Effect.gen(function* () {
28
- const config = yield* ConfigRepository;
29
- const issueRepo = yield* IssueRepository;
30
-
31
- yield* config.load(); // Ensure initialized
32
-
33
- // Get both tasks to get their IDs
34
- const blockerTask = yield* issueRepo
35
- .getTaskByIdentifier(blocker)
36
- .pipe(Effect.catchTag("TaskNotFoundError", () => issueRepo.getTask(blocker as TaskId)));
37
-
38
- const blockedTask = yield* issueRepo
39
- .getTaskByIdentifier(blocked)
40
- .pipe(Effect.catchTag("TaskNotFoundError", () => issueRepo.getTask(blocked as TaskId)));
41
-
42
- // Remove the blocking relationship
43
- yield* issueRepo.removeBlocker(blockedTask.id, blockerTask.id);
44
-
45
- if (json) {
46
- yield* Console.log(
47
- JSON.stringify({
48
- status: "unblocked",
49
- blocker: {
50
- id: blockerTask.id,
51
- identifier: blockerTask.identifier,
52
- },
53
- blocked: {
54
- id: blockedTask.id,
55
- identifier: blockedTask.identifier,
56
- },
57
- }),
58
- );
59
- } else {
60
- yield* Console.log(`${blockerTask.identifier} no longer blocks ${blockedTask.identifier}`);
61
- }
62
- }),
63
- );
@@ -1,125 +0,0 @@
1
- import * as Command from "@effect/cli/Command";
2
- import * as Args from "@effect/cli/Args";
3
- import * as Options from "@effect/cli/Options";
4
- import * as Effect from "effect/Effect";
5
- import * as Console from "effect/Console";
6
- import * as Option from "effect/Option";
7
- import { IssueRepository } from "../../../../ports/IssueRepository.js";
8
- import {
9
- UpdateTaskInput,
10
- type TaskId,
11
- type Priority,
12
- type TaskStatus,
13
- } from "../../../../domain/Task.js";
14
-
15
- const taskIdArg = Args.text({ name: "task-id" }).pipe(
16
- Args.withDescription("Task identifier (e.g., ENG-123)"),
17
- );
18
-
19
- const titleOption = Options.text("title").pipe(
20
- Options.withAlias("t"),
21
- Options.withDescription("New task title"),
22
- Options.optional,
23
- );
24
-
25
- const descriptionOption = Options.text("description").pipe(
26
- Options.withAlias("d"),
27
- Options.withDescription("New task description (use - to read from stdin)"),
28
- Options.optional,
29
- );
30
-
31
- const priorityOption = Options.choice("priority", ["urgent", "high", "medium", "low", "none"]).pipe(
32
- Options.withAlias("p"),
33
- Options.withDescription("New task priority"),
34
- Options.optional,
35
- );
36
-
37
- const statusOption = Options.choice("status", [
38
- "backlog",
39
- "todo",
40
- "in_progress",
41
- "in_review",
42
- "done",
43
- "cancelled",
44
- ]).pipe(Options.withAlias("s"), Options.withDescription("New task status"), Options.optional);
45
-
46
- const jsonOption = Options.boolean("json").pipe(
47
- Options.withDescription("Output as JSON"),
48
- Options.withDefault(false),
49
- );
50
-
51
- export const updateCommand = Command.make(
52
- "update",
53
- {
54
- taskId: taskIdArg,
55
- title: titleOption,
56
- description: descriptionOption,
57
- priority: priorityOption,
58
- status: statusOption,
59
- json: jsonOption,
60
- },
61
- ({ taskId, title, description, priority, status, json }) =>
62
- Effect.gen(function* () {
63
- const issueRepo = yield* IssueRepository;
64
-
65
- // Check if any update was provided
66
- const hasUpdate =
67
- Option.isSome(title) ||
68
- Option.isSome(description) ||
69
- Option.isSome(priority) ||
70
- Option.isSome(status);
71
-
72
- if (!hasUpdate) {
73
- yield* Console.error(
74
- "No updates provided. Use --title, --description, --priority, or --status.",
75
- );
76
- return;
77
- }
78
-
79
- // Get the task first to resolve identifier to ID
80
- const existingTask = yield* issueRepo
81
- .getTaskByIdentifier(taskId)
82
- .pipe(Effect.catchTag("TaskNotFoundError", () => issueRepo.getTask(taskId as TaskId)));
83
-
84
- // Build update input
85
- const input = new UpdateTaskInput({
86
- title: Option.isSome(title) ? Option.some(title.value) : Option.none(),
87
- description: Option.isSome(description) ? Option.some(description.value) : Option.none(),
88
- priority: Option.isSome(priority) ? Option.some(priority.value as Priority) : Option.none(),
89
- status: Option.isSome(status) ? Option.some(status.value as TaskStatus) : Option.none(),
90
- });
91
-
92
- const task = yield* issueRepo.updateTask(existingTask.id, input);
93
-
94
- if (json) {
95
- const output = {
96
- status: "updated",
97
- task: {
98
- id: task.id,
99
- identifier: task.identifier,
100
- title: task.title,
101
- description: Option.getOrNull(task.description),
102
- priority: task.priority,
103
- state: task.state.name,
104
- url: task.url,
105
- },
106
- };
107
- yield* Console.log(JSON.stringify(output, null, 2));
108
- } else {
109
- yield* Console.log(`Updated: ${task.identifier} - ${task.title}`);
110
- if (Option.isSome(title)) {
111
- yield* Console.log(`Title: ${task.title}`);
112
- }
113
- if (Option.isSome(description)) {
114
- yield* Console.log(`Description updated`);
115
- }
116
- if (Option.isSome(priority)) {
117
- yield* Console.log(`Priority: ${task.priority}`);
118
- }
119
- if (Option.isSome(status)) {
120
- yield* Console.log(`Status: ${task.state.name}`);
121
- }
122
- yield* Console.log(`URL: ${task.url}`);
123
- }
124
- }),
125
- );
@@ -1,76 +0,0 @@
1
- import * as Command from "@effect/cli/Command";
2
- import * as Console from "effect/Console";
3
- import { initCommand } from "./commands/init.js";
4
- import { loginCommand } from "./commands/login.js";
5
- import { teamCommand } from "./commands/team.js";
6
- import { projectCommand } from "./commands/project.js";
7
- import { readyCommand } from "./commands/ready.js";
8
- import { listCommand } from "./commands/list.js";
9
- import { showCommand } from "./commands/show.js";
10
- import { startCommand } from "./commands/start.js";
11
- import { doneCommand } from "./commands/done.js";
12
- import { createCommand } from "./commands/create.js";
13
- import { blockCommand } from "./commands/block.js";
14
- import { unblockCommand } from "./commands/unblock.js";
15
- import { blockedCommand } from "./commands/blocked.js";
16
- import { primeCommand } from "./commands/prime.js";
17
- import { updateCommand } from "./commands/update.js";
18
- import { relateCommand } from "./commands/relate.js";
19
-
20
- // Root command
21
- const ship = Command.make("ship", {}, () =>
22
- Console.log(`ship - Linear + jj workflow CLI
23
-
24
- Usage: ship <command> [options]
25
-
26
- Commands:
27
- init Initialize workspace and authenticate
28
- login Re-authenticate with Linear
29
- team Switch team
30
- project Switch project
31
-
32
- ready List tasks ready to work on (no blockers)
33
- blocked List blocked tasks
34
- list List all tasks with filters
35
- show <id> Show task details
36
- create <title> Create new task
37
-
38
- start <id> Start working on a task
39
- done <id> Mark task as complete
40
- update <id> Update task details
41
-
42
- block <a> <b> Mark task A as blocking task B
43
- unblock <a> <b> Remove blocking relationship
44
- relate <a> <b> Link two tasks as related
45
-
46
- prime Output AI-optimized context
47
-
48
- Run 'ship <command> --help' for more information on a command.`),
49
- );
50
-
51
- // Combine all commands
52
- export const command = ship.pipe(
53
- Command.withSubcommands([
54
- initCommand,
55
- loginCommand,
56
- teamCommand,
57
- projectCommand,
58
- readyCommand,
59
- listCommand,
60
- showCommand,
61
- startCommand,
62
- doneCommand,
63
- updateCommand,
64
- createCommand,
65
- blockCommand,
66
- unblockCommand,
67
- relateCommand,
68
- blockedCommand,
69
- primeCommand,
70
- ]),
71
- );
72
-
73
- export const run = Command.run(command, {
74
- name: "ship",
75
- version: "0.0.1",
76
- });
package/src/bin.ts DELETED
@@ -1,12 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
4
- import * as Effect from "effect/Effect";
5
- import { run } from "./adapters/driving/cli/main.js";
6
- import { AppLayer } from "./infrastructure/Layers.js";
7
-
8
- // Run the CLI
9
- run(process.argv).pipe(
10
- Effect.provide(AppLayer),
11
- NodeRuntime.runMain({ disableErrorReporting: false }),
12
- );
@@ -1,42 +0,0 @@
1
- import * as Schema from "effect/Schema";
2
- import { ProjectId, TeamId } from "./Task.js";
3
-
4
- // Personal API key from https://linear.app/settings/api
5
- export class AuthConfig extends Schema.Class<AuthConfig>("AuthConfig")({
6
- apiKey: Schema.String,
7
- }) {}
8
-
9
- export class LinearConfig extends Schema.Class<LinearConfig>("LinearConfig")({
10
- teamId: TeamId,
11
- teamKey: Schema.String,
12
- projectId: Schema.OptionFromNullOr(ProjectId),
13
- }) {}
14
-
15
- export class GitConfig extends Schema.Class<GitConfig>("GitConfig")({
16
- defaultBranch: Schema.optionalWith(Schema.String, { default: () => "main" }),
17
- }) {}
18
-
19
- export class PrConfig extends Schema.Class<PrConfig>("PrConfig")({
20
- openBrowser: Schema.optionalWith(Schema.Boolean, { default: () => true }),
21
- }) {}
22
-
23
- export class CommitConfig extends Schema.Class<CommitConfig>("CommitConfig")({
24
- conventionalFormat: Schema.optionalWith(Schema.Boolean, { default: () => true }),
25
- }) {}
26
-
27
- export class ShipConfig extends Schema.Class<ShipConfig>("ShipConfig")({
28
- linear: LinearConfig,
29
- auth: AuthConfig,
30
- git: Schema.optionalWith(GitConfig, { default: () => new GitConfig({}) }),
31
- pr: Schema.optionalWith(PrConfig, { default: () => new PrConfig({}) }),
32
- commit: Schema.optionalWith(CommitConfig, { default: () => new CommitConfig({}) }),
33
- }) {}
34
-
35
- // Partial config for when we're initializing
36
- export class PartialShipConfig extends Schema.Class<PartialShipConfig>("PartialShipConfig")({
37
- linear: Schema.OptionFromNullOr(LinearConfig),
38
- auth: Schema.OptionFromNullOr(AuthConfig),
39
- git: Schema.optionalWith(GitConfig, { default: () => new GitConfig({}) }),
40
- pr: Schema.optionalWith(PrConfig, { default: () => new PrConfig({}) }),
41
- commit: Schema.optionalWith(CommitConfig, { default: () => new CommitConfig({}) }),
42
- }) {}
@@ -1,89 +0,0 @@
1
- import * as Data from "effect/Data";
2
-
3
- // === Task Errors ===
4
-
5
- export class TaskNotFoundError extends Data.TaggedError("TaskNotFoundError")<{
6
- readonly taskId: string;
7
- }> {
8
- get message() {
9
- return `Task not found: ${this.taskId}`;
10
- }
11
- }
12
-
13
- export class TaskError extends Data.TaggedError("TaskError")<{
14
- readonly message: string;
15
- readonly cause?: unknown;
16
- }> {}
17
-
18
- // === Auth Errors ===
19
-
20
- export class AuthError extends Data.TaggedError("AuthError")<{
21
- readonly message: string;
22
- readonly cause?: unknown;
23
- }> {}
24
-
25
- export class NotAuthenticatedError extends Data.TaggedError("NotAuthenticatedError")<{
26
- readonly message: string;
27
- }> {}
28
-
29
- // === Config Errors ===
30
-
31
- export class ConfigError extends Data.TaggedError("ConfigError")<{
32
- readonly message: string;
33
- readonly cause?: unknown;
34
- }> {}
35
-
36
- export class ConfigNotFoundError extends Data.TaggedError("ConfigNotFoundError")<{
37
- readonly message: string;
38
- }> {}
39
-
40
- export class WorkspaceNotInitializedError extends Data.TaggedError("WorkspaceNotInitializedError")<{
41
- readonly message: string;
42
- }> {
43
- static readonly default = new WorkspaceNotInitializedError({
44
- message: "Workspace not initialized. Run 'ship init' first.",
45
- });
46
- }
47
-
48
- // === VCS Errors ===
49
-
50
- export class VcsError extends Data.TaggedError("VcsError")<{
51
- readonly message: string;
52
- readonly cause?: unknown;
53
- }> {}
54
-
55
- export class JjNotInstalledError extends Data.TaggedError("JjNotInstalledError")<{
56
- readonly message: string;
57
- }> {
58
- static readonly default = new JjNotInstalledError({
59
- message: "jj is not installed. Visit https://jj-vcs.github.io/jj/",
60
- });
61
- }
62
-
63
- // === PR Errors ===
64
-
65
- export class PrError extends Data.TaggedError("PrError")<{
66
- readonly message: string;
67
- readonly cause?: unknown;
68
- }> {}
69
-
70
- export class GhNotInstalledError extends Data.TaggedError("GhNotInstalledError")<{
71
- readonly message: string;
72
- }> {
73
- static readonly default = new GhNotInstalledError({
74
- message: "gh CLI is not installed. Visit https://cli.github.com/",
75
- });
76
- }
77
-
78
- // === Linear API Errors ===
79
-
80
- export class LinearApiError extends Data.TaggedError("LinearApiError")<{
81
- readonly message: string;
82
- readonly statusCode?: number;
83
- readonly cause?: unknown;
84
- }> {}
85
-
86
- export class RateLimitError extends Data.TaggedError("RateLimitError")<{
87
- readonly message: string;
88
- readonly retryAfter?: number;
89
- }> {}