@lightward/mechanic-cli 0.1.1 → 0.1.4

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 (49) hide show
  1. package/README.md +75 -37
  2. package/dist/base-command.d.ts +1 -0
  3. package/dist/base-command.d.ts.map +1 -1
  4. package/dist/base-command.js +16 -1
  5. package/dist/client.d.ts +3 -1
  6. package/dist/client.d.ts.map +1 -1
  7. package/dist/client.js +4 -1
  8. package/dist/commands/auth/login.d.ts.map +1 -1
  9. package/dist/commands/auth/login.js +6 -2
  10. package/dist/commands/doctor.d.ts.map +1 -1
  11. package/dist/commands/doctor.js +9 -7
  12. package/dist/commands/init.d.ts.map +1 -1
  13. package/dist/commands/init.js +12 -6
  14. package/dist/commands/tasks/bundle.d.ts.map +1 -1
  15. package/dist/commands/tasks/bundle.js +37 -5
  16. package/dist/commands/tasks/diff.d.ts.map +1 -1
  17. package/dist/commands/tasks/diff.js +2 -2
  18. package/dist/commands/tasks/new.d.ts +16 -0
  19. package/dist/commands/tasks/new.d.ts.map +1 -0
  20. package/dist/commands/tasks/new.js +104 -0
  21. package/dist/commands/tasks/open.d.ts.map +1 -1
  22. package/dist/commands/tasks/open.js +2 -2
  23. package/dist/commands/tasks/preview.d.ts +4 -0
  24. package/dist/commands/tasks/preview.d.ts.map +1 -1
  25. package/dist/commands/tasks/preview.js +43 -3
  26. package/dist/commands/tasks/publish.js +1 -1
  27. package/dist/commands/tasks/pull.d.ts.map +1 -1
  28. package/dist/commands/tasks/pull.js +8 -5
  29. package/dist/commands/tasks/push.d.ts +2 -0
  30. package/dist/commands/tasks/push.d.ts.map +1 -1
  31. package/dist/commands/tasks/push.js +22 -7
  32. package/dist/commands/tasks/status.d.ts +1 -1
  33. package/dist/commands/tasks/status.d.ts.map +1 -1
  34. package/dist/commands/tasks/status.js +26 -14
  35. package/dist/commands/tasks/unbundle.d.ts.map +1 -1
  36. package/dist/commands/tasks/unbundle.js +5 -5
  37. package/dist/commands/tasks/validate.d.ts +1 -0
  38. package/dist/commands/tasks/validate.d.ts.map +1 -1
  39. package/dist/commands/tasks/validate.js +40 -18
  40. package/dist/config.d.ts +1 -1
  41. package/dist/config.d.ts.map +1 -1
  42. package/dist/config.js +19 -11
  43. package/dist/tasks.js +2 -2
  44. package/dist/types.d.ts +12 -0
  45. package/dist/types.d.ts.map +1 -1
  46. package/dist/update-check.d.ts +14 -0
  47. package/dist/update-check.d.ts.map +1 -0
  48. package/dist/update-check.js +136 -0
  49. package/package.json +1 -1
@@ -0,0 +1,104 @@
1
+ import { Args, Flags } from "@oclif/core";
2
+ import path from "node:path";
3
+ import { BaseCommand } from "../../base-command.js";
4
+ import { CliError } from "../../errors.js";
5
+ import { pathExists } from "../../fs.js";
6
+ import { displayTaskPath, taskPath, taskSlug, unbundleTask, writeTaskFilePath } from "../../tasks.js";
7
+ function taskNameFromInput(input) {
8
+ const cleaned = input.trim().replace(/\s+/g, " ");
9
+ if (cleaned.includes(" ")) {
10
+ return cleaned;
11
+ }
12
+ return taskSlug(cleaned)
13
+ .split("-")
14
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
15
+ .join(" ");
16
+ }
17
+ function starterTask(name) {
18
+ return {
19
+ halt_action_run_sequence_on_error: false,
20
+ name,
21
+ docs: "",
22
+ options: {},
23
+ perform_action_runs_in_sequence: false,
24
+ preview_event_definitions: [],
25
+ script: [
26
+ "{% comment %}",
27
+ " Start writing your Mechanic task here.",
28
+ "{% endcomment %}",
29
+ "",
30
+ '{% log message: "Hello from Mechanic" %}',
31
+ "",
32
+ ].join("\n"),
33
+ subscriptions_template: "mechanic/user/text\n",
34
+ tags: [],
35
+ };
36
+ }
37
+ export default class TasksNew extends BaseCommand {
38
+ static summary = "Create a new blank local task.";
39
+ static description = "Create one blank local starter task JSON file and its matching editable helper directory. Nothing is created in Mechanic until you publish.";
40
+ static examples = [
41
+ "$ mechanic tasks new order-tagger",
42
+ "$ mechanic tasks new \"Order tagger\"",
43
+ ];
44
+ static args = {
45
+ name: Args.string({ required: true, description: "Task name or slug for the new local task." }),
46
+ };
47
+ static flags = {
48
+ force: Flags.boolean({ description: "Overwrite the local starter task JSON file and helper directory files." }),
49
+ json: Flags.boolean({ description: "Print created paths as JSON for agents, scripts, or editor integrations." }),
50
+ };
51
+ async run() {
52
+ const { args, flags } = await this.parse(TasksNew);
53
+ const project = await this.loadProject();
54
+ const requestedName = args.name.trim();
55
+ if (!requestedName) {
56
+ throw new CliError("Task name cannot be blank.", 2);
57
+ }
58
+ const slug = taskSlug(requestedName);
59
+ const name = taskNameFromInput(requestedName);
60
+ const filePath = taskPath(project, slug);
61
+ const helperDir = filePath.replace(/\.json$/i, "");
62
+ await this.checkExistingPaths(project, filePath, helperDir, Boolean(flags.force));
63
+ await writeTaskFilePath(filePath, starterTask(name));
64
+ await unbundleTask(filePath, helperDir);
65
+ const fileDisplay = displayTaskPath(project, filePath);
66
+ const helperDisplay = displayTaskPath(project, helperDir);
67
+ if (flags.json) {
68
+ this.outputJson({
69
+ ok: true,
70
+ name,
71
+ slug,
72
+ file: fileDisplay,
73
+ helper_dir: helperDisplay,
74
+ });
75
+ return;
76
+ }
77
+ this.log(`Created ${this.taskName(fileDisplay)}`);
78
+ this.log(`Created ${this.taskName(helperDisplay)}`);
79
+ this.log("");
80
+ this.log("Next steps:");
81
+ this.log(` edit ${this.taskName(displayTaskPath(project, path.join(helperDir, "script.liquid")))}`);
82
+ this.log(` mechanic tasks preview ${this.taskName(slug)}`);
83
+ this.log(` mechanic tasks bundle ${this.taskName(slug)}`);
84
+ this.log(` mechanic tasks publish ${this.taskName(slug)}`);
85
+ }
86
+ async checkExistingPaths(project, filePath, helperDir, force) {
87
+ if (force) {
88
+ return;
89
+ }
90
+ const existing = [];
91
+ if (await pathExists(filePath)) {
92
+ existing.push(displayTaskPath(project, filePath));
93
+ }
94
+ if (await pathExists(helperDir)) {
95
+ existing.push(displayTaskPath(project, helperDir));
96
+ }
97
+ if (existing.length > 0) {
98
+ throw new CliError([
99
+ `${existing.join(" and ")} already exists.`,
100
+ "Choose another task name, or re-run with --force to overwrite the local starter files.",
101
+ ].join("\n"), 2);
102
+ }
103
+ }
104
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"open.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/open.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAIpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAc9C,MAAM,CAAC,OAAO,OAAO,SAAU,SAAQ,WAAW;IAChD,OAAgB,OAAO,SAAqC;IAC5D,OAAgB,WAAW,SAAuH;IAElJ,OAAgB,IAAI;;MAElB;IAEI,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAiBlE,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBnC,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAgB3B"}
1
+ {"version":3,"file":"open.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/open.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAIpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAc9C,MAAM,CAAC,OAAO,OAAO,SAAU,SAAQ,WAAW;IAChD,OAAgB,OAAO,SAAqC;IAC5D,OAAgB,WAAW,SAAgH;IAE3I,OAAgB,IAAI;;MAElB;IAEI,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAiBlE,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBnC,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAgB3B"}
@@ -15,9 +15,9 @@ function openerCommand(url) {
15
15
  }
16
16
  export default class TasksOpen extends BaseCommand {
17
17
  static summary = "Open a remote task in Mechanic.";
18
- static description = "Open a remote Mechanic task by task ID, linked local task JSON file, helper directory, or unique local task slug.";
18
+ static description = "Open a remote Mechanic task by local task slug, task ID, linked local task JSON file, or helper directory.";
19
19
  static args = {
20
- target: Args.string({ required: true, description: "Remote task ID, linked local task JSON file, helper directory, or unique local task slug." }),
20
+ target: Args.string({ required: true, description: "Local task slug, remote task ID, linked local task JSON file, or helper directory." }),
21
21
  };
22
22
  async taskIdForTarget(project, target) {
23
23
  const selector = await resolveTaskSelector(project, target, {
@@ -1,6 +1,7 @@
1
1
  import { BaseCommand } from "../../base-command.js";
2
2
  import type { MechanicClient } from "../../client.js";
3
3
  import type { JsonObject, Project, TaskPreviewResponse } from "../../types.js";
4
+ type PreviewDiagnostic = NonNullable<TaskPreviewResponse["diagnostics"]>[number];
4
5
  type PreparedPreview = {
5
6
  label: string;
6
7
  slug?: string;
@@ -28,6 +29,9 @@ export default class TasksPreview extends BaseCommand {
28
29
  renderPreview(project: Project, prepared: PreparedPreview, response: TaskPreviewResponse, options: PreviewRenderOptions): void;
29
30
  renderVerboseDetails(response: TaskPreviewResponse): void;
30
31
  renderRunDetails(response: TaskPreviewResponse): void;
32
+ renderDiagnostics(response: TaskPreviewResponse, options: PreviewRenderOptions): void;
33
+ diagnosticSeverityLabel(severity: PreviewDiagnostic["severity"]): string;
34
+ diagnosticDetails(diagnostic: PreviewDiagnostic): string[];
31
35
  renderPreviewValue(label: string, value: unknown, indent: number): void;
32
36
  formatPreviewValue(value: unknown): string | null;
33
37
  runStatusLabel(ok: boolean | null): string;
@@ -1 +1 @@
1
- {"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/preview.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAYpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE/E,KAAK,eAAe,GAAG;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,KAAK,oBAAoB,GAAG;IAC1B,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAWF,MAAM,CAAC,OAAO,OAAO,YAAa,SAAQ,WAAW;IACnD,OAAgB,OAAO,SAAmD;IAC1E,OAAgB,WAAW,SAId;IAEb,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;;;MAInB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB1B,aAAa,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IAS5C,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC;IA0D3F,cAAc,CAClB,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,eAAe,EACzB,MAAM,EAAE,OAAO,GACd,OAAO,CAAC,mBAAmB,CAAC;IA4B/B,aAAa,CACX,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,eAAe,EACzB,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE,oBAAoB,GAC5B,IAAI;IA+BP,oBAAoB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IAuDzD,gBAAgB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IA+BrD,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAiBvE,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI;IAgBjD,cAAc,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,GAAG,MAAM;IAY1C,iBAAiB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IA4BtD,sBAAsB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IAmB3D,YAAY,CAAC,QAAQ,EAAE,eAAe,EAAE,QAAQ,EAAE,mBAAmB,GAAG,MAAM;IAI9E,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,QAAQ,EAAE,mBAAmB,GAAG,MAAM;IA+BjG,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE;IAIxC,iBAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAWlD,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBpH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAapC,WAAW,CAAC,MAAM,EAAE,mBAAmB,CAAC,QAAQ,CAAC,GAAG,MAAM;CAU3D"}
1
+ {"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/preview.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAapD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE/E,KAAK,iBAAiB,GAAG,WAAW,CAAC,mBAAmB,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjF,KAAK,eAAe,GAAG;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,KAAK,oBAAoB,GAAG;IAC1B,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAWF,MAAM,CAAC,OAAO,OAAO,YAAa,SAAQ,WAAW;IACnD,OAAgB,OAAO,SAAmD;IAC1E,OAAgB,WAAW,SAId;IAEb,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;;;MAInB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB1B,aAAa,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IAS5C,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC;IA0D3F,cAAc,CAClB,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,eAAe,EACzB,MAAM,EAAE,OAAO,GACd,OAAO,CAAC,mBAAmB,CAAC;IA4B/B,aAAa,CACX,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,eAAe,EACzB,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE,oBAAoB,GAC5B,IAAI;IAgCP,oBAAoB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IAuDzD,gBAAgB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IA+BrD,iBAAiB,CAAC,QAAQ,EAAE,mBAAmB,EAAE,OAAO,EAAE,oBAAoB,GAAG,IAAI;IAyBrF,uBAAuB,CAAC,QAAQ,EAAE,iBAAiB,CAAC,UAAU,CAAC,GAAG,MAAM;IAWxE,iBAAiB,CAAC,UAAU,EAAE,iBAAiB,GAAG,MAAM,EAAE;IAU1D,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAiBvE,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI;IAgBjD,cAAc,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,GAAG,MAAM;IAY1C,iBAAiB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IA4BtD,sBAAsB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IAmB3D,YAAY,CAAC,QAAQ,EAAE,eAAe,EAAE,QAAQ,EAAE,mBAAmB,GAAG,MAAM;IAI9E,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,QAAQ,EAAE,mBAAmB,GAAG,MAAM;IA+BjG,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE;IAIxC,iBAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAWlD,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBpH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAapC,WAAW,CAAC,MAAM,EAAE,mBAAmB,CAAC,QAAQ,CAAC,GAAG,MAAM;CAU3D"}
@@ -2,7 +2,7 @@ import { Args, Flags } from "@oclif/core";
2
2
  import { BaseCommand } from "../../base-command.js";
3
3
  import { HttpError, CliError } from "../../errors.js";
4
4
  import { pathExists } from "../../fs.js";
5
- import { displayTaskPath, readRawTaskFile, resolveTaskSelector, rawTaskFromHelperDir, taskForPush, unbundledHelperDirForTaskFile, validateTaskForPush, } from "../../tasks.js";
5
+ import { displayTaskPath, readRawTaskFile, resolveTaskSelector, rawTaskFromHelperDir, taskForPush, slugFromTaskFile, unbundledHelperDirForTaskFile, validateTaskForPush, } from "../../tasks.js";
6
6
  function errorBodyAsPreview(body) {
7
7
  if (!body || typeof body !== "object") {
8
8
  return null;
@@ -18,7 +18,7 @@ export default class TasksPreview extends BaseCommand {
18
18
  "Use --remote to preview the current task already in Mechanic, or --json for agents, scripts, and CI.",
19
19
  ].join("\n");
20
20
  static args = {
21
- target: Args.string({ required: true, description: "Task file, helper directory, linked task ID, or unique local task slug." }),
21
+ target: Args.string({ required: true, description: "Local task slug, task file, helper directory, or linked task ID." }),
22
22
  };
23
23
  static flags = {
24
24
  json: Flags.boolean({ description: "Print the raw preview response as JSON for agents or CI." }),
@@ -134,6 +134,7 @@ export default class TasksPreview extends BaseCommand {
134
134
  if (options.verbose) {
135
135
  this.renderVerboseDetails(response);
136
136
  }
137
+ this.renderDiagnostics(response, options);
137
138
  this.renderPermissions(response);
138
139
  if (response.status === "invalid") {
139
140
  this.renderValidationErrors(response);
@@ -217,6 +218,45 @@ export default class TasksPreview extends BaseCommand {
217
218
  this.table(rows);
218
219
  }
219
220
  }
221
+ renderDiagnostics(response, options) {
222
+ const diagnostics = response.diagnostics || [];
223
+ const visibleDiagnostics = options.verbose
224
+ ? diagnostics
225
+ : diagnostics.filter((diagnostic) => diagnostic.severity === "warning" || diagnostic.severity === "error");
226
+ if (visibleDiagnostics.length === 0) {
227
+ return;
228
+ }
229
+ this.log("");
230
+ this.log(this.accent("Notes"));
231
+ for (const diagnostic of visibleDiagnostics) {
232
+ const details = options.verbose ? this.diagnosticDetails(diagnostic) : [];
233
+ const suffix = details.length ? ` ${this.muted(`(${details.join(", ")})`)}` : "";
234
+ const code = options.verbose ? ` ${this.muted(`[${diagnostic.code}]`)}` : "";
235
+ this.log(` ${this.diagnosticSeverityLabel(diagnostic.severity)}${code} ${diagnostic.message}${suffix}`);
236
+ if (options.verbose && diagnostic.docs_url) {
237
+ this.log(` ${this.muted(`Docs: ${diagnostic.docs_url}`)}`);
238
+ }
239
+ }
240
+ }
241
+ diagnosticSeverityLabel(severity) {
242
+ switch (severity) {
243
+ case "error":
244
+ return this.color("red", "error");
245
+ case "warning":
246
+ return this.color("yellow", "warning");
247
+ case "info":
248
+ return this.muted("info");
249
+ }
250
+ }
251
+ diagnosticDetails(diagnostic) {
252
+ return [
253
+ diagnostic.event_topic ? `event ${diagnostic.event_topic}` : null,
254
+ typeof diagnostic.event_index === "number" ? `event #${diagnostic.event_index + 1}` : null,
255
+ typeof diagnostic.task_run_index === "number" ? `task run #${diagnostic.task_run_index + 1}` : null,
256
+ typeof diagnostic.action_run_index === "number" ? `action run #${diagnostic.action_run_index + 1}` : null,
257
+ diagnostic.action_type ? `action ${diagnostic.action_type}` : null,
258
+ ].filter((detail) => Boolean(detail));
259
+ }
220
260
  renderPreviewValue(label, value, indent) {
221
261
  const formatted = this.formatPreviewValue(value);
222
262
  if (!formatted) {
@@ -345,7 +385,7 @@ export default class TasksPreview extends BaseCommand {
345
385
  `${relativeFile} is out of date with ${relativeHelperDir}.`,
346
386
  "",
347
387
  "Bundle the helper directory before previewing this JSON file:",
348
- ` mechanic tasks bundle ${relativeHelperDir}`,
388
+ ` mechanic tasks bundle ${slugFromTaskFile(filePath)}`,
349
389
  ].join("\n"), 2);
350
390
  }
351
391
  approvalHint(action) {
@@ -5,7 +5,7 @@ export default class TasksPublish extends TasksPush {
5
5
  static summary = "Publish one local task to Mechanic.";
6
6
  static description = "Publish one local task to Mechanic, or use --all to publish every local task JSON file.";
7
7
  static args = {
8
- file: Args.string({ required: false, description: "Task file, helper directory, linked task ID, or unique local task slug." }),
8
+ file: Args.string({ required: false, description: "Local task slug, task file, helper directory, or linked task ID." }),
9
9
  };
10
10
  static flags = {
11
11
  all: Flags.boolean({ description: "Publish every task JSON file in this project." }),
@@ -1 +1 @@
1
- {"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/pull.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAmBpD,MAAM,CAAC,OAAO,OAAO,SAAU,SAAQ,WAAW;IAChD,OAAgB,OAAO,SAAmD;IAC1E,OAAgB,WAAW,SAAmI;IAE9J,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;;MAGnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CA2F3B"}
1
+ {"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/pull.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAoBpD,MAAM,CAAC,OAAO,OAAO,SAAU,SAAQ,WAAW;IAChD,OAAgB,OAAO,SAAmD;IAC1E,OAAgB,WAAW,SAAmI;IAE9J,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;;MAGnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAsG3B"}
@@ -6,12 +6,12 @@ import { pathExists } from "../../fs.js";
6
6
  import { contentHash } from "../../hash.js";
7
7
  import { stableStringify } from "../../json.js";
8
8
  import { CliError } from "../../errors.js";
9
- import { displayTaskPath, nextAvailableSlug, readRawTaskFile, resolveTaskSelector, taskForPush, taskPath, taskSlug, unbundledHelperDirForTaskFile, writeTaskFilePathAndRefreshHelper, } from "../../tasks.js";
9
+ import { displayTaskPath, nextAvailableSlug, readRawTaskFile, resolveTaskSelector, slugFromTaskFile, taskForPush, taskPath, taskSlug, unbundledHelperDirForTaskFile, writeTaskFilePathAndRefreshHelper, } from "../../tasks.js";
10
10
  export default class TasksPull extends BaseCommand {
11
11
  static summary = "Pull remote tasks into local task JSON files.";
12
12
  static description = "Pull every remote task into local canonical task JSON files, or pass a task ID, linked file, or linked slug to pull one task.";
13
13
  static args = {
14
- task: Args.string({ required: false, description: "Remote task ID, linked local task JSON file, or linked local task slug." }),
14
+ task: Args.string({ required: false, description: "Local task slug, remote task ID, or linked local task JSON file." }),
15
15
  };
16
16
  static flags = {
17
17
  all: Flags.boolean({ description: "Pull every remote task for this shop. This is the default when no task ID is passed." }),
@@ -45,13 +45,14 @@ export default class TasksPull extends BaseCommand {
45
45
  if (helperDir) {
46
46
  const relativeFile = displayTaskPath(project, filePath);
47
47
  const relativeHelperDir = displayTaskPath(project, helperDir);
48
+ const slug = slugFromTaskFile(filePath);
48
49
  throw new CliError([
49
50
  `${relativeFile} is out of date with ${relativeHelperDir}.`,
50
51
  "",
51
52
  "The helper directory has changes that are not bundled into the JSON file.",
52
53
  "",
53
54
  "Bundle first:",
54
- ` mechanic tasks bundle ${relativeHelperDir}`,
55
+ ` mechanic tasks bundle ${slug}`,
55
56
  "",
56
57
  "Then pull:",
57
58
  ` mechanic tasks pull ${envelope.id}`,
@@ -61,7 +62,9 @@ export default class TasksPull extends BaseCommand {
61
62
  const remoteTaskForPull = taskForPush(envelope.task);
62
63
  const localMatchesCurrentRemote = stableStringify(localTaskForPull) === stableStringify(remoteTaskForPull);
63
64
  const localHash = contentHash(localTaskForPull);
64
- if (!localMatchesCurrentRemote && localHash !== link?.last_remote_content_hash) {
65
+ const localMatchesLastWritten = (localHash === link?.last_remote_content_hash
66
+ || localHash === link?.last_local_content_hash);
67
+ if (!localMatchesCurrentRemote && !localMatchesLastWritten) {
65
68
  throw new CliError([
66
69
  `${displayTaskPath(project, filePath)} has local changes that would be overwritten.`,
67
70
  `Run "mechanic tasks diff ${displayTaskPath(project, filePath)}" to review them.`,
@@ -71,7 +74,7 @@ export default class TasksPull extends BaseCommand {
71
74
  }
72
75
  }
73
76
  await writeTaskFilePathAndRefreshHelper(filePath, envelope.task);
74
- links = updateLink({ ...project, links }, slug, envelope.id, envelope.content_hash);
77
+ links = updateLink({ ...project, links }, slug, envelope.id, envelope.content_hash, contentHash(taskForPush(envelope.task)));
75
78
  await saveLinks(project, links);
76
79
  rows.push([
77
80
  this.taskId(project, envelope.id),
@@ -18,6 +18,7 @@ type PushRow = {
18
18
  task_id: string | null;
19
19
  content_hash: string | null;
20
20
  details: string;
21
+ url?: string;
21
22
  };
22
23
  type CreateCollision = {
23
24
  remoteId: string;
@@ -54,6 +55,7 @@ export default class TasksPush extends BaseCommand {
54
55
  rows: PushRow[];
55
56
  }>;
56
57
  pushRowsToTable(project: Project, statusHeader: string, rows: PushRow[], label: (status: string) => string): string[][];
58
+ printCreatedTaskLinks(rows: PushRow[]): void;
57
59
  planLabel(plan: string): string;
58
60
  }
59
61
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/push.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAkBpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnF,KAAK,YAAY,GAAG;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,UAAU,CAAC;CAClB,CAAC;AAEF,KAAK,aAAa,GAAG;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,IAAI,EAAE,OAAO,EAAE,CAAC;CACjB,CAAC;AAEF,KAAK,OAAO,GAAG;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,KAAK,cAAc,GAAG;IACpB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAgBF,MAAM,CAAC,OAAO,OAAO,SAAU,SAAQ,WAAW;IAChD,OAAgB,MAAM,UAAQ;IAC9B,OAAgB,OAAO,SAAuE;IAC9F,OAAgB,WAAW,SAA6G;IAExI,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;;;;MAKnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAMpB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAwCvE,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAgE9F,sBAAsB,CAC1B,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,SAAS,EAChB,aAAa,EAAE,YAAY,EAAE,GAC5B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IA8B/B,wBAAwB,CAC5B,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,SAAS,EAChB,aAAa,EAAE,YAAY,EAAE,GAC5B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAwClC,6BAA6B,CACjC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,SAAS,EAChB,aAAa,EAAE,YAAY,EAAE,GAC5B,OAAO,CAAC,IAAI,CAAC;IAwBV,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,kBAAkB,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC;IA6FpG,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;QACtF,OAAO,EAAE,OAAO,CAAC;QACjB,IAAI,EAAE,OAAO,EAAE,CAAC;KACjB,CAAC;IA6EF,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,EAAE,EAAE;IAavH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAehC"}
1
+ {"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/push.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,9 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { Args, Flags } from "@oclif/core";
3
3
  import { BaseCommand } from "../../base-command.js";
4
- import { linkForSlug, saveLinks, updateLink } from "../../config.js";
4
+ import { linkForSlug, saveLinks, taskAdminUrl, updateLink } from "../../config.js";
5
5
  import { CliError } from "../../errors.js";
6
+ import { contentHash } from "../../hash.js";
6
7
  import { stableStringify } from "../../json.js";
7
8
  import { displayTaskPath, remoteChangedSinceLastPull, remoteChangedSinceLastPullDetails, remoteChangedSinceLastPullMessage, selectedTaskFiles, readRawTaskFile, slugFromTaskFile, taskForPush, taskSlug, unbundledHelperDirForTaskFile, validateTaskForPush, writeTaskFilePathAndRefreshHelper, } from "../../tasks.js";
8
9
  function createTaskIdempotencyKey(shopDomain, slug) {
@@ -22,7 +23,7 @@ export default class TasksPush extends BaseCommand {
22
23
  static summary = "Publish one local task, or use --all to publish every local task.";
23
24
  static description = "Publish one local task to Mechanic, or use --all to publish every local task JSON file in this project.";
24
25
  static args = {
25
- file: Args.string({ required: false, description: "Task file, helper directory, linked task ID, or unique local task slug." }),
26
+ file: Args.string({ required: false, description: "Local task slug, task file, helper directory, or linked task ID." }),
26
27
  };
27
28
  static flags = {
28
29
  all: Flags.boolean({ description: "Publish every task JSON file in this project." }),
@@ -67,6 +68,7 @@ export default class TasksPush extends BaseCommand {
67
68
  return;
68
69
  }
69
70
  this.table(this.pushRowsToTable(project, "Action", rows, (status) => this.actionLabel(status)));
71
+ this.printCreatedTaskLinks(rows);
70
72
  }
71
73
  async publishTasks(project, preparation, force) {
72
74
  const client = await this.verifiedClientForProject(project);
@@ -80,7 +82,7 @@ export default class TasksPush extends BaseCommand {
80
82
  const link = linkForSlug({ ...project, links }, slug);
81
83
  if (!link) {
82
84
  const created = await client.createTask(task, createTaskIdempotencyKey(project.shopDomain, slug));
83
- links = updateLink({ ...project, links }, slug, created.id, created.content_hash);
85
+ links = updateLink({ ...project, links }, slug, created.id, created.content_hash, contentHash(taskForPush(created.task)));
84
86
  await saveLinks(project, links);
85
87
  await writeTaskFilePathAndRefreshHelper(file, created.task);
86
88
  rows.push({
@@ -89,6 +91,7 @@ export default class TasksPush extends BaseCommand {
89
91
  task_id: created.id,
90
92
  content_hash: created.content_hash,
91
93
  details: "created disabled; enable in Mechanic",
94
+ url: taskAdminUrl(project, created.id),
92
95
  });
93
96
  continue;
94
97
  }
@@ -109,7 +112,7 @@ export default class TasksPush extends BaseCommand {
109
112
  task,
110
113
  ...(force ? { force: true } : { previous_content_hash: link.last_remote_content_hash }),
111
114
  });
112
- links = updateLink({ ...project, links }, slug, updated.id, updated.content_hash);
115
+ links = updateLink({ ...project, links }, slug, updated.id, updated.content_hash, contentHash(taskForPush(updated.task)));
113
116
  await saveLinks(project, links);
114
117
  await writeTaskFilePathAndRefreshHelper(file, updated.task);
115
118
  rows.push({
@@ -118,6 +121,7 @@ export default class TasksPush extends BaseCommand {
118
121
  task_id: updated.id,
119
122
  content_hash: updated.content_hash,
120
123
  details: "",
124
+ url: taskAdminUrl(project, updated.id),
121
125
  });
122
126
  }
123
127
  return rows;
@@ -249,10 +253,10 @@ export default class TasksPush extends BaseCommand {
249
253
  "The helper directory has changes that are not bundled into the JSON file.",
250
254
  "",
251
255
  "Bundle first:",
252
- ` mechanic tasks bundle ${relativeHelperDir}`,
256
+ ` mechanic tasks bundle ${slug}`,
253
257
  "",
254
258
  "Then publish:",
255
- ` mechanic tasks publish ${relativeFile}`,
259
+ ` mechanic tasks publish ${slug}`,
256
260
  ].join("\n");
257
261
  if (!collectBlockedRows) {
258
262
  throw new CliError(message, 2);
@@ -263,7 +267,7 @@ export default class TasksPush extends BaseCommand {
263
267
  status: "blocked",
264
268
  task_id: null,
265
269
  content_hash: null,
266
- details: `bundle ${relativeHelperDir} first`,
270
+ details: `bundle ${slug} first`,
267
271
  });
268
272
  continue;
269
273
  }
@@ -352,6 +356,17 @@ export default class TasksPush extends BaseCommand {
352
356
  ]),
353
357
  ];
354
358
  }
359
+ printCreatedTaskLinks(rows) {
360
+ const createdRows = rows.filter((row) => row.status === "created" && row.url);
361
+ if (createdRows.length === 0) {
362
+ return;
363
+ }
364
+ this.log("");
365
+ this.log(this.accent(createdRows.length === 1 ? "Open created task:" : "Open created tasks:"));
366
+ for (const row of createdRows) {
367
+ this.log(` ${this.taskName(row.file)} ${this.taskName(row.url || "")}`);
368
+ }
369
+ }
355
370
  planLabel(plan) {
356
371
  switch (plan) {
357
372
  case "would create":
@@ -15,7 +15,7 @@ export default class TasksStatus extends BaseCommand {
15
15
  };
16
16
  static flags: {
17
17
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
18
- remote: import("@oclif/core/interfaces").BooleanFlag<boolean>;
18
+ local: import("@oclif/core/interfaces").BooleanFlag<boolean>;
19
19
  };
20
20
  run(): Promise<void>;
21
21
  helperStatus(project: Project, file: string, task: JsonObject): Promise<HelperStatus>;
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/status.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAe9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAE1D,KAAK,YAAY,GAAG;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAqBF,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,WAAW;IAClD,OAAgB,OAAO,SAAuE;IAC9F,OAAgB,WAAW,SAA0H;IAErJ,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;;MAQnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAsGpB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC;IA4BrF,YAAY,CAChB,MAAM,EAAE,cAAc,GAAG,IAAI,EAC7B,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,GACnC,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAsB/C,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAejC,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;CAgBnC"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/status.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAe9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAE1D,KAAK,YAAY,GAAG;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAuBF,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,WAAW;IAClD,OAAgB,OAAO,SAAwE;IAC/F,OAAgB,WAAW,SAA+Q;IAE1S,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;;MAOnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAsHpB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC;IA4BrF,YAAY,CAChB,MAAM,EAAE,cAAc,GAAG,IAAI,EAC7B,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,GACnC,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAsB/C,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAejC,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;CAgBnC"}
@@ -5,22 +5,22 @@ import { linkForSlug } from "../../config.js";
5
5
  import { pathExists } from "../../fs.js";
6
6
  import { stableStringify } from "../../json.js";
7
7
  import { displayTaskPath, helperDirForTaskFile, readRawTaskFile, remoteChangedSinceLastPull, remoteChangedSinceLastPullDetails, selectedTaskFiles, slugFromTaskFile, taskForPush, unbundledHelperDirForTaskFile, validateTaskForPush, } from "../../tasks.js";
8
+ const IMPLICIT_REMOTE_CHECK_LIMIT = 25;
8
9
  function errorMessage(error) {
9
10
  return error instanceof Error ? error.message : String(error);
10
11
  }
11
12
  export default class TasksStatus extends BaseCommand {
12
- static summary = "Show local task link, readiness, and optional remote sync status.";
13
- static description = "Show whether local task files are linked, ready to publish, and optionally in sync with their remote Mechanic tasks.";
13
+ static summary = "Show whether local task files are ready and in sync with Mechanic.";
14
+ static description = `Show whether local task files are linked, ready to publish, and in sync with their remote Mechanic tasks. Repo-wide status checks remote state for up to ${IMPLICIT_REMOTE_CHECK_LIMIT} tasks; larger projects show local status only. Use --local to skip remote checks.`;
14
15
  static args = {
15
- file: Args.string({ required: false, description: "Task file, helper directory, linked task ID, or unique local task slug. Defaults to every task JSON file." }),
16
+ file: Args.string({ required: false, description: "Local task slug, task file, helper directory, or linked task ID. Defaults to every task JSON file." }),
16
17
  };
17
18
  static flags = {
18
19
  json: Flags.boolean({
19
20
  description: "Print task status as JSON for agents, scripts, or editor integrations.",
20
21
  }),
21
- remote: Flags.boolean({
22
- char: "r",
23
- description: "Check linked remote tasks and report no-change, local-change, or conflict state.",
22
+ local: Flags.boolean({
23
+ description: "Only check local files and links; do not call Mechanic.",
24
24
  }),
25
25
  };
26
26
  async run() {
@@ -29,6 +29,8 @@ export default class TasksStatus extends BaseCommand {
29
29
  const files = await selectedTaskFiles(project, args.file, !args.file);
30
30
  const statuses = [];
31
31
  let client = null;
32
+ const remoteSkippedForLimit = !flags.local && !args.file && files.length > IMPLICIT_REMOTE_CHECK_LIMIT;
33
+ const checkRemote = !flags.local && !remoteSkippedForLimit;
32
34
  const getClient = async () => {
33
35
  client ||= await this.verifiedClientForProject(project);
34
36
  return client;
@@ -64,9 +66,9 @@ export default class TasksStatus extends BaseCommand {
64
66
  if (helperStatus.details) {
65
67
  details.push(helperStatus.details);
66
68
  }
67
- const remote = flags.remote && task && !helperStatus.blocked
69
+ const remote = checkRemote && task && !helperStatus.blocked
68
70
  ? await this.remoteStatus(link ? await getClient() : null, task, link)
69
- : flags.remote ? { label: "skipped", details: undefined } : null;
71
+ : checkRemote ? { label: "skipped", details: undefined } : null;
70
72
  if (remote?.details) {
71
73
  details.push(remote.details);
72
74
  }
@@ -86,14 +88,19 @@ export default class TasksStatus extends BaseCommand {
86
88
  });
87
89
  }
88
90
  if (flags.json) {
89
- this.outputJson({
91
+ const output = {
90
92
  shop_domain: project.shopDomain,
91
- remote_checked: Boolean(flags.remote),
93
+ remote_checked: checkRemote,
92
94
  tasks: statuses,
93
- });
95
+ };
96
+ if (remoteSkippedForLimit) {
97
+ output.remote_skipped_reason = "too_many_tasks";
98
+ output.remote_check_limit = IMPLICIT_REMOTE_CHECK_LIMIT;
99
+ }
100
+ this.outputJson(output);
94
101
  return;
95
102
  }
96
- const rows = flags.remote
103
+ const rows = checkRemote
97
104
  ? [["File", "Link", "Local", "Remote", "Task ID", "Details"]]
98
105
  : [["File", "Link", "Local", "Task ID", "Details"]];
99
106
  for (const status of statuses) {
@@ -102,12 +109,17 @@ export default class TasksStatus extends BaseCommand {
102
109
  status.link.linked ? this.success("linked") : this.color("yellow", "unlinked"),
103
110
  this.localLabel(status.local.label),
104
111
  ];
105
- if (flags.remote) {
112
+ if (checkRemote) {
106
113
  row.push(this.remoteLabel(status.remote?.label || "not checked"));
107
114
  }
108
115
  row.push(status.link.remote_id ? this.taskId(project, status.link.remote_id) : "--", status.details.join("; "));
109
116
  rows.push(row);
110
117
  }
118
+ if (remoteSkippedForLimit) {
119
+ this.log(this.color("yellow", `Skipped remote checks for ${files.length} tasks (limit ${IMPLICIT_REMOTE_CHECK_LIMIT}).`));
120
+ this.log(`Run "${this.taskName("mechanic tasks status <task>")}" to check one task's remote state.`);
121
+ this.log("");
122
+ }
111
123
  this.table(rows);
112
124
  }
113
125
  async helperStatus(project, file, task) {
@@ -121,7 +133,7 @@ export default class TasksStatus extends BaseCommand {
121
133
  return {
122
134
  label: "needs bundle",
123
135
  blocked: true,
124
- details: `run mechanic tasks bundle ${displayTaskPath(project, driftDir)}`,
136
+ details: `run mechanic tasks bundle ${slugFromTaskFile(file)}`,
125
137
  };
126
138
  }
127
139
  }
@@ -1 +1 @@
1
- {"version":3,"file":"unbundle.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/unbundle.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAKpD,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,WAAW;IACpD,OAAgB,OAAO,SAAkD;IACzE,OAAgB,WAAW,SAAiJ;IAC5K,OAAgB,QAAQ,WAKtB;IAEF,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;;MAOnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;YA+BZ,oBAAoB;CA2CnC"}
1
+ {"version":3,"file":"unbundle.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/unbundle.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAKpD,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,WAAW;IACpD,OAAgB,OAAO,SAAkD;IACzE,OAAgB,WAAW,SAA0I;IACrK,OAAgB,QAAQ,WAKtB;IAEF,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;;MAOnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;YA+BZ,oBAAoB;CA2CnC"}
@@ -6,15 +6,15 @@ import { pathExists } from "../../fs.js";
6
6
  import { displayTaskPath, resolveTaskSelector, unbundleTask } from "../../tasks.js";
7
7
  export default class TasksUnbundle extends BaseCommand {
8
8
  static summary = "Unbundle one task into one helper directory.";
9
- static description = "Unbundle one canonical task JSON file, linked task ID, helper directory, or unique local task slug into one editable helper task directory.";
9
+ static description = "Unbundle one local task slug, canonical task JSON file, linked task ID, or helper directory into one editable helper task directory.";
10
10
  static examples = [
11
- "$ mechanic tasks unbundle tasks/order-tagger.json",
12
11
  "$ mechanic tasks unbundle order-tagger",
12
+ "$ mechanic tasks unbundle tasks/order-tagger.json",
13
13
  "$ mechanic tasks unbundle 171578bf-79e2-46af-857a-dbd71c6b7b2b",
14
- "$ mechanic tasks unbundle tasks/order-tagger.json --out helpers/order-tagger",
14
+ "$ mechanic tasks unbundle order-tagger --out helpers/order-tagger",
15
15
  ];
16
16
  static args = {
17
- file: Args.string({ required: false, description: "Task file, helper directory, linked task ID, or unique local task slug." }),
17
+ file: Args.string({ required: false, description: "Local task slug, task file, helper directory, or linked task ID." }),
18
18
  };
19
19
  static flags = {
20
20
  json: Flags.boolean({
@@ -31,7 +31,7 @@ export default class TasksUnbundle extends BaseCommand {
31
31
  "Choose one task to unbundle.",
32
32
  "",
33
33
  "Example:",
34
- " mechanic tasks unbundle tasks/order-tagger.json",
34
+ " mechanic tasks unbundle order-tagger",
35
35
  "",
36
36
  "By default this writes to tasks/order-tagger/. Use --out <dir> to choose another helper directory.",
37
37
  ].join("\n"), 2);
@@ -11,5 +11,6 @@ export default class TasksValidate extends BaseCommand {
11
11
  };
12
12
  run(): Promise<void>;
13
13
  validateTaskFile(file: string): Promise<ValidationResult>;
14
+ validateTaskDirectory(target: string): Promise<ValidationResult[]>;
14
15
  }
15
16
  //# sourceMappingURL=validate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/validate.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGpD,OAAO,EAKL,KAAK,gBAAgB,EACtB,MAAM,gBAAgB,CAAC;AAMxB,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,WAAW;IACpD,OAAgB,OAAO,SAAgF;IACvG,OAAgB,WAAW,SAA0G;IAErI,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;MAInB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAiDpB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAchE"}
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../src/commands/tasks/validate.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGpD,OAAO,EAML,KAAK,gBAAgB,EACtB,MAAM,gBAAgB,CAAC;AAUxB,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,WAAW;IACpD,OAAgB,OAAO,SAA8D;IACrF,OAAgB,WAAW,SAAqH;IAEhJ,OAAgB,IAAI;;MAElB;IAEF,OAAgB,KAAK;;MAInB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAoDpB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAezD,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;CAgBzE"}