@morit/cli 1.0.0 → 1.1.1

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 (35) hide show
  1. package/README.md +53 -18
  2. package/assets/docs/README.md +105 -0
  3. package/assets/docs/ai-response-and-timeline.md +125 -0
  4. package/assets/docs/ai-skill-and-docx-workflow.md +83 -0
  5. package/assets/docs/app-builder.md +56 -0
  6. package/assets/docs/authentication.md +140 -0
  7. package/assets/docs/components.md +159 -0
  8. package/assets/docs/design-tokens-responsive.md +148 -0
  9. package/assets/docs/docs-index.json +93 -0
  10. package/assets/docs/examples-notion.md +83 -0
  11. package/assets/docs/examples-school-life.md +74 -0
  12. package/assets/docs/getting-started.md +132 -0
  13. package/assets/docs/information-hierarchy.md +81 -0
  14. package/assets/docs/instances-and-connectors.md +93 -0
  15. package/assets/docs/lifecycle-and-api.md +158 -0
  16. package/assets/docs/local-cli.md +124 -0
  17. package/assets/docs/manifest.md +208 -0
  18. package/assets/docs/packaging-and-testing.md +115 -0
  19. package/assets/docs/permissions-and-data.md +131 -0
  20. package/assets/docs/platform-compatibility.md +62 -0
  21. package/assets/docs/project-structure.md +102 -0
  22. package/assets/docs/remote-mcp.md +152 -0
  23. package/assets/docs/school-life-privacy.md +49 -0
  24. package/assets/docs/screens-layout-navigation.md +95 -0
  25. package/assets/docs/sdk-and-mcp.md +182 -0
  26. package/assets/docs/tool-and-skill.md +163 -0
  27. package/assets/docs/troubleshooting.md +117 -0
  28. package/assets/docs/ui-extensions.md +70 -0
  29. package/assets/docs/ui-runtime-v2.md +273 -0
  30. package/assets/docs/verification.md +128 -0
  31. package/assets/plugin_contract.json +222 -1
  32. package/package.json +1 -1
  33. package/src/cli.js +27 -3
  34. package/src/secure-store.js +113 -43
  35. package/src/workspace.js +297 -86
@@ -1,5 +1,5 @@
1
1
  {
2
- "sdk_version": "1.7.5",
2
+ "sdk_version": "1.7.6",
3
3
  "manifest_schema_versions": [1, 2],
4
4
  "recommended_schema_version": 2,
5
5
  "package_content_type": "application/vnd.morit.plugin+zip",
@@ -88,10 +88,20 @@
88
88
  "row",
89
89
  "wrap",
90
90
  "grid",
91
+ "stack",
92
+ "positioned",
93
+ "scroll",
94
+ "padding",
95
+ "center",
96
+ "expanded",
97
+ "surface",
91
98
  "card",
92
99
  "section",
93
100
  "text",
94
101
  "icon",
102
+ "image",
103
+ "avatar",
104
+ "badge",
95
105
  "divider",
96
106
  "spacer",
97
107
  "metric",
@@ -110,6 +120,217 @@
110
120
  "switch",
111
121
  "empty"
112
122
  ],
123
+ "ui_runtime": {
124
+ "schema_version": 2,
125
+ "config_fields": [
126
+ "ui_schema",
127
+ "icon",
128
+ "description",
129
+ "placement",
130
+ "app_bar",
131
+ "navigation",
132
+ "theme",
133
+ "initial_state",
134
+ "data_sources",
135
+ "view"
136
+ ],
137
+ "node_fields": ["id", "type", "props", "children", "action", "visible_when"],
138
+ "prop_fields": [
139
+ "text",
140
+ "title",
141
+ "subtitle",
142
+ "label",
143
+ "supporting",
144
+ "icon",
145
+ "style",
146
+ "tone",
147
+ "align",
148
+ "max_lines",
149
+ "spacing",
150
+ "padding",
151
+ "columns",
152
+ "stack_at",
153
+ "min_item_width",
154
+ "size",
155
+ "value",
156
+ "source",
157
+ "empty_text",
158
+ "limit",
159
+ "state_key",
160
+ "placeholder",
161
+ "input_type",
162
+ "options",
163
+ "persist",
164
+ "selected",
165
+ "dense",
166
+ "chart_type",
167
+ "x_key",
168
+ "y_key",
169
+ "date_key",
170
+ "title_key",
171
+ "show_legend",
172
+ "full_width",
173
+ "width",
174
+ "height",
175
+ "min_width",
176
+ "max_width",
177
+ "min_height",
178
+ "max_height",
179
+ "margin",
180
+ "alignment",
181
+ "main_axis_alignment",
182
+ "cross_axis_alignment",
183
+ "main_axis_size",
184
+ "color",
185
+ "background_color",
186
+ "foreground_color",
187
+ "border_color",
188
+ "border_width",
189
+ "border_radius",
190
+ "elevation",
191
+ "opacity",
192
+ "aspect_ratio",
193
+ "fit",
194
+ "asset",
195
+ "url",
196
+ "tooltip",
197
+ "semantic_label",
198
+ "exclude_semantics",
199
+ "scroll_direction",
200
+ "clip",
201
+ "shrink_wrap",
202
+ "enabled",
203
+ "flex",
204
+ "left",
205
+ "top",
206
+ "right",
207
+ "bottom"
208
+ ],
209
+ "app_bar_fields": ["title", "subtitle", "center_title", "pinned", "leading", "actions"],
210
+ "navigation_fields": ["type", "items", "selected_state_key", "persist", "label_behavior", "rail_breakpoint"],
211
+ "menu_item_fields": ["id", "label", "icon", "selected_icon", "value", "show_as", "action"],
212
+ "theme_fields": ["color_scheme", "radius", "spacing", "density"],
213
+ "action_fields": ["type", "capability", "query", "arguments", "store", "target", "source", "values", "persist", "transition", "replace"],
214
+ "actions": ["invoke", "navigate", "set_state", "refresh", "back"],
215
+ "navigation_transitions": ["platform", "fade", "slide", "none"],
216
+ "navigation_types": ["tabs", "bar", "rail", "drawer", "adaptive"],
217
+ "icon_tokens": [
218
+ "add",
219
+ "analytics",
220
+ "arrow",
221
+ "arrow_back",
222
+ "arrow_forward",
223
+ "bell",
224
+ "calendar",
225
+ "check",
226
+ "clock",
227
+ "close",
228
+ "delete",
229
+ "description",
230
+ "download",
231
+ "edit",
232
+ "education",
233
+ "error",
234
+ "event",
235
+ "extension",
236
+ "favorite",
237
+ "file",
238
+ "folder",
239
+ "help",
240
+ "home",
241
+ "home_filled",
242
+ "inbox",
243
+ "info",
244
+ "link",
245
+ "list",
246
+ "meal",
247
+ "menu",
248
+ "more",
249
+ "notification",
250
+ "notifications",
251
+ "person",
252
+ "refresh",
253
+ "schedule",
254
+ "school",
255
+ "school.settings",
256
+ "search",
257
+ "settings",
258
+ "share",
259
+ "sparkle",
260
+ "upload"
261
+ ],
262
+ "color_formats": ["material_semantic_token", "#RRGGBB", "#AARRGGBB"],
263
+ "material_color_tokens": [
264
+ "primary",
265
+ "on_primary",
266
+ "primary_container",
267
+ "on_primary_container",
268
+ "secondary",
269
+ "on_secondary",
270
+ "secondary_container",
271
+ "on_secondary_container",
272
+ "tertiary",
273
+ "on_tertiary",
274
+ "tertiary_container",
275
+ "on_tertiary_container",
276
+ "error",
277
+ "on_error",
278
+ "error_container",
279
+ "on_error_container",
280
+ "surface",
281
+ "on_surface",
282
+ "surface_variant",
283
+ "on_surface_variant",
284
+ "outline",
285
+ "outline_variant",
286
+ "inverse_surface",
287
+ "inverse_on_surface",
288
+ "inverse_primary",
289
+ "shadow",
290
+ "scrim",
291
+ "transparent"
292
+ ],
293
+ "theme_color_roles": [
294
+ "primary",
295
+ "on_primary",
296
+ "primary_container",
297
+ "on_primary_container",
298
+ "secondary",
299
+ "on_secondary",
300
+ "secondary_container",
301
+ "on_secondary_container",
302
+ "tertiary",
303
+ "on_tertiary",
304
+ "tertiary_container",
305
+ "on_tertiary_container",
306
+ "error",
307
+ "on_error",
308
+ "error_container",
309
+ "on_error_container",
310
+ "surface",
311
+ "on_surface",
312
+ "surface_variant",
313
+ "on_surface_variant",
314
+ "outline",
315
+ "outline_variant",
316
+ "inverse_surface",
317
+ "inverse_on_surface",
318
+ "inverse_primary",
319
+ "shadow",
320
+ "scrim"
321
+ ],
322
+ "image_sources": ["package_asset", "capability_result_binding"],
323
+ "platforms": ["android", "ios", "desktop"],
324
+ "limits": {
325
+ "nodes": 160,
326
+ "depth": 12,
327
+ "children_per_node": 32,
328
+ "data_sources": 8,
329
+ "state_keys": 32,
330
+ "navigation_items": 8,
331
+ "app_bar_actions": 6
332
+ }
333
+ },
113
334
  "signature": {
114
335
  "algorithm": "ed25519",
115
336
  "embedded_public_key": true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morit/cli",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Official Morit Developer CLI for Cloud Projects, validation, builds, and deployments",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -259,6 +259,15 @@ export async function pullPluginProject(root, client, force = false) {
259
259
  return project;
260
260
  }
261
261
 
262
+ async function saveLoginCredential(save, credential) {
263
+ try {
264
+ await save(credential);
265
+ } catch (error) {
266
+ const reason = error instanceof Error ? error.message : "secure credential storage failed";
267
+ throw new Error(`Authorization succeeded, but the token could not be stored safely: ${reason}. Run morit logout to recover, then login again`);
268
+ }
269
+ }
270
+
262
271
  async function login(parsed, options, stdout) {
263
272
  const apiUrl = parsed.flags["api-url"] || DEFAULT_API_URL;
264
273
  const fetchImpl = options.fetchImpl || fetch;
@@ -266,7 +275,11 @@ async function login(parsed, options, stdout) {
266
275
  if (directToken) {
267
276
  const client = new MoritCloudClient({ apiUrl, token: directToken, fetchImpl });
268
277
  const identity = await client.whoami();
269
- await (options.saveCredential || saveCredential)({ token: directToken, apiUrl: client.apiUrl, organizationId: identity.organization_id });
278
+ await saveLoginCredential(options.saveCredential || saveCredential, {
279
+ token: directToken,
280
+ apiUrl: client.apiUrl,
281
+ organizationId: identity.organization_id,
282
+ });
270
283
  output(stdout, parsed.flags.json ? identity : `Signed in to organization ${identity.organization_id}`, parsed.flags.json);
271
284
  return 0;
272
285
  }
@@ -282,6 +295,7 @@ async function login(parsed, options, stdout) {
282
295
  stdout.write(`Open ${authorization.verification_uri}\nEnter code: ${authorization.user_code}\n`);
283
296
  (options.openBrowser || openBrowser)(authorization.verification_uri_complete);
284
297
  const deadline = Date.now() + authorization.expires_in * 1000;
298
+ let networkFailures = 0;
285
299
  while (Date.now() < deadline) {
286
300
  await (options.sleep || ((milliseconds) => new Promise((resolveSleep) => setTimeout(resolveSleep, milliseconds))))(authorization.interval * 1000);
287
301
  try {
@@ -293,7 +307,7 @@ async function login(parsed, options, stdout) {
293
307
  retries: 0,
294
308
  }),
295
309
  );
296
- await (options.saveCredential || saveCredential)({
310
+ await saveLoginCredential(options.saveCredential || saveCredential, {
297
311
  token: token.access_token,
298
312
  apiUrl: client.apiUrl,
299
313
  organizationId: token.organization_id,
@@ -301,7 +315,17 @@ async function login(parsed, options, stdout) {
301
315
  output(stdout, parsed.flags.json ? { organization_id: token.organization_id, scopes: token.scopes } : "Morit CLI is connected", parsed.flags.json);
302
316
  return 0;
303
317
  } catch (error) {
304
- if (error instanceof MoritCloudError && error.code === "authorization_pending") continue;
318
+ if (error instanceof MoritCloudError && error.code === "authorization_pending") {
319
+ networkFailures = 0;
320
+ continue;
321
+ }
322
+ if (error instanceof MoritCloudError && error.code === "network_error" && ++networkFailures < 3) continue;
323
+ if (error instanceof MoritCloudError && error.code === "access_denied") {
324
+ throw new Error("CLI authorization was cancelled");
325
+ }
326
+ if (error instanceof MoritCloudError && ["expired_token", "device_code_expired"].includes(error.code)) {
327
+ throw new Error("CLI authorization expired; run morit login again");
328
+ }
305
329
  throw error;
306
330
  }
307
331
  }
@@ -1,4 +1,4 @@
1
- import { execFileSync, spawnSync } from "node:child_process";
1
+ import { spawnSync } from "node:child_process";
2
2
  import { chmod, mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
3
3
  import { homedir, userInfo } from "node:os";
4
4
  import { join } from "node:path";
@@ -8,14 +8,20 @@ const credentialDirectory = join(homedir(), ".morit");
8
8
  const credentialPath = join(credentialDirectory, "credentials.json");
9
9
  const serviceName = "morit-cli";
10
10
  const ACCESS_TOKEN = /^sk_live_[A-Za-z0-9]{32}$/;
11
+ const STORAGE_BY_PLATFORM = { win32: "dpapi", darwin: "keychain", linux: "secret-tool" };
11
12
 
12
13
  function powershell(script, input = "") {
13
14
  const result = spawnSync(
14
15
  "powershell.exe",
15
- ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", script],
16
+ ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", `$ErrorActionPreference='Stop';Add-Type -AssemblyName System.Security;${script}`],
16
17
  { input, encoding: "utf8", windowsHide: true, maxBuffer: 1024 * 1024 },
17
18
  );
18
- if (result.status !== 0) throw new Error("Windows credential protection failed");
19
+ if (result.error?.code === "ENOENT") {
20
+ throw new Error("Windows PowerShell is required to protect Morit credentials");
21
+ }
22
+ if (result.status !== 0 || !result.stdout.trim()) {
23
+ throw new Error("Windows credential protection failed; verify that Windows PowerShell and DPAPI are available");
24
+ }
19
25
  return result.stdout.trim();
20
26
  }
21
27
 
@@ -38,55 +44,115 @@ function windowsUnprotect(payload) {
38
44
  }
39
45
 
40
46
  function linuxSecretToolAvailable() {
41
- try {
42
- execFileSync("secret-tool", ["--version"], { stdio: "ignore" });
43
- return true;
44
- } catch {
45
- return false;
47
+ const result = spawnSync("secret-tool", ["--help"], { stdio: "ignore" });
48
+ return !result.error && result.status === 0;
49
+ }
50
+
51
+ function nativeCredential(storage, action, account, token) {
52
+ let command;
53
+ let args;
54
+ if (storage === "keychain") {
55
+ command = "security";
56
+ args = action === "store"
57
+ ? ["add-generic-password", "-a", account, "-s", serviceName, "-w", token, "-U"]
58
+ : action === "read"
59
+ ? ["find-generic-password", "-a", account, "-s", serviceName, "-w"]
60
+ : ["delete-generic-password", "-a", account, "-s", serviceName];
61
+ } else {
62
+ if (!linuxSecretToolAvailable()) {
63
+ throw new Error("Linux Secret Service is unavailable; install secret-tool and unlock a desktop keyring, or use MORIT_ACCESS_TOKEN for this session");
64
+ }
65
+ command = "secret-tool";
66
+ args = action === "store"
67
+ ? ["store", "--label=Morit CLI", "service", serviceName, "account", account]
68
+ : action === "read"
69
+ ? ["lookup", "service", serviceName, "account", account]
70
+ : ["clear", "service", serviceName, "account", account];
46
71
  }
72
+ const result = spawnSync(command, args, {
73
+ input: action === "store" && storage === "secret-tool" ? token : undefined,
74
+ encoding: "utf8",
75
+ windowsHide: true,
76
+ maxBuffer: 1024 * 1024,
77
+ });
78
+ const label = storage === "keychain" ? "macOS Keychain" : "Linux Secret Service";
79
+ const stdout = result.stdout?.trim() || "";
80
+ const stderr = result.stderr?.trim() || "";
81
+ const missing = action === "delete" && (
82
+ (storage === "keychain" && result.status === 44)
83
+ || (storage === "secret-tool" && result.status === 1 && !stderr)
84
+ );
85
+ if (result.error?.code === "ENOENT") throw new Error(`${label} command is unavailable`);
86
+ if (result.status !== 0 && !missing) {
87
+ throw new Error(`Unable to ${action} the Morit token in ${label}; unlock the secure store and retry`);
88
+ }
89
+ if (action === "read" && !stdout) {
90
+ throw new Error(`The saved Morit token is missing from ${label}; run morit logout and login again`);
91
+ }
92
+ return stdout;
93
+ }
94
+
95
+ function validMetadata(value) {
96
+ if (!value || typeof value !== "object" || value.version !== 1 || typeof value.api_url !== "string") return false;
97
+ if (value.storage === "dpapi") return typeof value.payload === "string" && value.payload.length > 0;
98
+ return (value.storage === "keychain" || value.storage === "secret-tool")
99
+ && typeof value.account === "string" && value.account.length > 0 && value.account.length <= 256;
47
100
  }
48
101
 
49
- async function metadata() {
50
- try { return JSON.parse(await readFile(credentialPath, "utf8")); }
51
- catch (error) {
102
+ async function metadata({ allowCorrupt = false } = {}) {
103
+ try {
104
+ const value = JSON.parse(await readFile(credentialPath, "utf8"));
105
+ if (!validMetadata(value)) throw new Error("invalid metadata");
106
+ return value;
107
+ } catch (error) {
52
108
  if (error.code === "ENOENT") return null;
109
+ if (allowCorrupt) return { corrupt: true };
53
110
  throw new Error("Morit credential metadata is unreadable; run morit logout and login again");
54
111
  }
55
112
  }
56
113
 
57
114
  async function writeMetadata(value) {
58
115
  await mkdir(credentialDirectory, { recursive: true, mode: 0o700 });
59
- const temporary = `${credentialPath}.tmp`;
60
- await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
61
- await rename(temporary, credentialPath);
62
- await chmod(credentialPath, 0o600).catch(() => undefined);
116
+ await chmod(credentialDirectory, 0o700).catch(() => undefined);
117
+ const temporary = `${credentialPath}.${process.pid}.tmp`;
118
+ try {
119
+ await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
120
+ await rename(temporary, credentialPath);
121
+ await chmod(credentialPath, 0o600).catch(() => undefined);
122
+ } catch (error) {
123
+ await unlink(temporary).catch(() => undefined);
124
+ throw new Error("Unable to update Morit credential metadata", { cause: error });
125
+ }
63
126
  }
64
127
 
65
128
  export async function saveCredential({ token, apiUrl, organizationId }) {
66
129
  if (!ACCESS_TOKEN.test(token || "")) throw new Error("Developer access token is invalid");
67
130
  const normalizedApiUrl = normalizeApiOrigin(apiUrl);
68
- const account = userInfo().username;
69
- if (process.platform === "win32") {
70
- await writeMetadata({ version: 1, storage: "dpapi", payload: windowsProtect(token), api_url: normalizedApiUrl, organization_id: organizationId });
71
- return;
131
+ const previous = await metadata();
132
+ const storage = STORAGE_BY_PLATFORM[process.platform];
133
+ if (!storage) throw new Error(`Secure credential storage is not supported on ${process.platform}`);
134
+ if (previous && previous.storage !== storage) {
135
+ throw new Error("Saved credentials belong to a different platform; run morit logout before logging in again");
72
136
  }
73
- if (process.platform === "darwin") {
74
- const result = spawnSync("security", ["add-generic-password", "-a", account, "-s", serviceName, "-w", token, "-U"], {
75
- stdio: "ignore",
76
- });
77
- if (result.status !== 0) throw new Error("Unable to save token in macOS Keychain");
78
- await writeMetadata({ version: 1, storage: "keychain", account, api_url: normalizedApiUrl, organization_id: organizationId });
137
+ if (storage === "dpapi") {
138
+ await writeMetadata({ version: 1, storage, payload: windowsProtect(token), api_url: normalizedApiUrl, organization_id: organizationId });
79
139
  return;
80
140
  }
81
- if (!linuxSecretToolAvailable()) {
82
- throw new Error("A desktop keyring is required; install secret-tool or use MORIT_ACCESS_TOKEN for this session");
141
+
142
+ const account = userInfo().username;
143
+ const previousToken = previous ? nativeCredential(storage, "read", previous.account) : null;
144
+ nativeCredential(storage, "store", account, token);
145
+ try {
146
+ await writeMetadata({ version: 1, storage, account, api_url: normalizedApiUrl, organization_id: organizationId });
147
+ } catch (error) {
148
+ try {
149
+ if (previousToken) nativeCredential(storage, "store", previous.account, previousToken);
150
+ else nativeCredential(storage, "delete", account);
151
+ } catch {
152
+ throw new Error("Credential metadata failed and secure-store recovery also failed; run morit logout before retrying");
153
+ }
154
+ throw error;
83
155
  }
84
- const result = spawnSync("secret-tool", ["store", "--label=Morit CLI", "service", serviceName, "account", account], {
85
- input: token,
86
- encoding: "utf8",
87
- });
88
- if (result.status !== 0) throw new Error("Unable to save token in the desktop keyring");
89
- await writeMetadata({ version: 1, storage: "secret-tool", account, api_url: normalizedApiUrl, organization_id: organizationId });
90
156
  }
91
157
 
92
158
  export async function loadCredential() {
@@ -107,24 +173,28 @@ export async function loadCredential() {
107
173
  if (value.storage === "dpapi" && process.platform === "win32") {
108
174
  token = windowsUnprotect(value.payload);
109
175
  } else if (value.storage === "keychain" && process.platform === "darwin") {
110
- token = execFileSync("security", ["find-generic-password", "-a", value.account, "-s", serviceName, "-w"], { encoding: "utf8" }).trim();
111
- } else if (value.storage === "secret-tool" && linuxSecretToolAvailable()) {
112
- token = execFileSync("secret-tool", ["lookup", "service", serviceName, "account", value.account], { encoding: "utf8" }).trim();
176
+ token = nativeCredential(value.storage, "read", value.account);
177
+ } else if (value.storage === "secret-tool" && process.platform === "linux") {
178
+ token = nativeCredential(value.storage, "read", value.account);
113
179
  } else {
114
180
  throw new Error("Saved credentials belong to a different platform; run morit logout and login again");
115
181
  }
116
- if (!ACCESS_TOKEN.test(token)) throw new Error("Saved Morit credential is invalid");
182
+ if (!ACCESS_TOKEN.test(token)) throw new Error("Saved Morit credential is invalid; run morit logout and login again");
117
183
  return { token, apiUrl, organizationId: value.organization_id, ephemeral: false };
118
184
  }
119
185
 
120
186
  export async function clearCredential() {
121
- const value = await metadata();
122
- if (value?.storage === "keychain" && process.platform === "darwin") {
123
- spawnSync("security", ["delete-generic-password", "-a", value.account, "-s", serviceName], { stdio: "ignore" });
124
- } else if (value?.storage === "secret-tool" && linuxSecretToolAvailable()) {
125
- spawnSync("secret-tool", ["clear", "service", serviceName, "account", value.account], { stdio: "ignore" });
187
+ const value = await metadata({ allowCorrupt: true });
188
+ if (!value) return;
189
+ if (value.corrupt) {
190
+ if (process.platform === "darwin") nativeCredential("keychain", "delete", userInfo().username);
191
+ else if (process.platform === "linux") nativeCredential("secret-tool", "delete", userInfo().username);
192
+ } else if (value.storage === "keychain" && process.platform === "darwin") {
193
+ nativeCredential(value.storage, "delete", value.account);
194
+ } else if (value.storage === "secret-tool" && process.platform === "linux") {
195
+ nativeCredential(value.storage, "delete", value.account);
126
196
  }
127
197
  await unlink(credentialPath).catch((error) => {
128
- if (error.code !== "ENOENT") throw error;
198
+ if (error.code !== "ENOENT") throw new Error("Unable to remove Morit credential metadata", { cause: error });
129
199
  });
130
200
  }