@1medium/cli 1.8.0 → 1.9.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 (3) hide show
  1. package/package.json +1 -1
  2. package/src/api.js +144 -0
  3. package/src/index.js +261 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1medium/cli",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "CLI and MCP server for 1Medium AI task management",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/api.js CHANGED
@@ -89,6 +89,54 @@ async function request(method, path, body = null, params = null) {
89
89
  return data;
90
90
  }
91
91
 
92
+ /**
93
+ * Make an authenticated API request to non-agent routes (e.g. /scripts, /script-events)
94
+ */
95
+ async function requestDirect(method, path, body = null, params = null) {
96
+ const baseUrl = getApiUrl();
97
+ const token = getToken();
98
+
99
+ let url = `${baseUrl}${path}`;
100
+ if (params) {
101
+ const searchParams = new URLSearchParams();
102
+ for (const [key, value] of Object.entries(params)) {
103
+ if (value !== undefined && value !== null) {
104
+ searchParams.append(key, value);
105
+ }
106
+ }
107
+ const queryString = searchParams.toString();
108
+ if (queryString) {
109
+ url += `?${queryString}`;
110
+ }
111
+ }
112
+
113
+ const options = {
114
+ method,
115
+ headers: {
116
+ Authorization: `Bearer ${token}`,
117
+ "Content-Type": "application/json",
118
+ "User-Agent": "1m-cli/1.0.0",
119
+ },
120
+ };
121
+
122
+ if (body && (method === "POST" || method === "PATCH" || method === "PUT")) {
123
+ options.body = JSON.stringify(body);
124
+ }
125
+
126
+ const response = await fetch(url, options);
127
+ const data = await response.json();
128
+
129
+ if (!response.ok) {
130
+ const message = data.message || data.error || "API request failed";
131
+ const error = new Error(message);
132
+ error.statusCode = response.status;
133
+ error.details = data.details;
134
+ throw error;
135
+ }
136
+
137
+ return data;
138
+ }
139
+
92
140
  /**
93
141
  * Token introspection
94
142
  */
@@ -208,6 +256,91 @@ async function deleteProject(id) {
208
256
  return request("DELETE", `/projects/${id}`);
209
257
  }
210
258
 
259
+ // ============================================================================
260
+ // Script API
261
+ // ============================================================================
262
+
263
+ /**
264
+ * List scripts
265
+ */
266
+ async function listScripts(params = {}) {
267
+ return requestDirect("GET", "/scripts", null, params);
268
+ }
269
+
270
+ /**
271
+ * Get a single script
272
+ */
273
+ async function getScript(id) {
274
+ return requestDirect("GET", `/scripts/${id}`);
275
+ }
276
+
277
+ /**
278
+ * Push (create/update) a script
279
+ */
280
+ async function pushScript(payload) {
281
+ return requestDirect("POST", "/scripts/push", payload);
282
+ }
283
+
284
+ /**
285
+ * Deprecate a script
286
+ */
287
+ async function deprecateScript(id) {
288
+ return requestDirect("POST", `/scripts/${id}/deprecate`);
289
+ }
290
+
291
+ // ============================================================================
292
+ // Script Event API
293
+ // ============================================================================
294
+
295
+ /**
296
+ * List script events
297
+ */
298
+ async function listScriptEvents(params = {}) {
299
+ return requestDirect("GET", "/script-events", null, params);
300
+ }
301
+
302
+ /**
303
+ * Get a single script event
304
+ */
305
+ async function getScriptEvent(id) {
306
+ return requestDirect("GET", `/script-events/${id}`);
307
+ }
308
+
309
+ /**
310
+ * Create a script event
311
+ */
312
+ async function createScriptEvent(payload) {
313
+ return requestDirect("POST", "/script-events", payload);
314
+ }
315
+
316
+ /**
317
+ * Update a script event
318
+ */
319
+ async function updateScriptEvent(id, payload) {
320
+ return requestDirect("PUT", `/script-events/${id}`, payload);
321
+ }
322
+
323
+ /**
324
+ * Delete a script event
325
+ */
326
+ async function deleteScriptEvent(id) {
327
+ return requestDirect("DELETE", `/script-events/${id}`);
328
+ }
329
+
330
+ /**
331
+ * Pause a script event
332
+ */
333
+ async function pauseScriptEvent(id) {
334
+ return requestDirect("POST", `/script-events/${id}/pause`);
335
+ }
336
+
337
+ /**
338
+ * Resume a script event
339
+ */
340
+ async function resumeScriptEvent(id) {
341
+ return requestDirect("POST", `/script-events/${id}/resume`);
342
+ }
343
+
211
344
  module.exports = {
212
345
  whoami,
213
346
  createTask,
@@ -226,4 +359,15 @@ module.exports = {
226
359
  createProject,
227
360
  updateProject,
228
361
  deleteProject,
362
+ listScripts,
363
+ getScript,
364
+ pushScript,
365
+ deprecateScript,
366
+ listScriptEvents,
367
+ getScriptEvent,
368
+ createScriptEvent,
369
+ updateScriptEvent,
370
+ deleteScriptEvent,
371
+ pauseScriptEvent,
372
+ resumeScriptEvent,
229
373
  };
package/src/index.js CHANGED
@@ -714,6 +714,267 @@ program
714
714
  }
715
715
  });
716
716
 
717
+ // ============================================================================
718
+ // Script Commands
719
+ // ============================================================================
720
+
721
+ const scriptCmd = program.command("script").description("Manage scripts");
722
+
723
+ scriptCmd
724
+ .command("push <file>")
725
+ .description("Push a script from a local file")
726
+ .option("-n, --name <name>", "Script name (defaults to filename)")
727
+ .option("--trigger <trigger>", "Trigger type: onCreate, onUpdate, onTag, schedule", "schedule")
728
+ .option("-j, --json", "Output as JSON")
729
+ .action(async (file, options) => {
730
+ try {
731
+ const fs = require("fs");
732
+ const path = require("path");
733
+
734
+ if (!fs.existsSync(file)) {
735
+ console.error(chalk.red(`Error: File not found: ${file}`));
736
+ process.exit(1);
737
+ }
738
+
739
+ const code = fs.readFileSync(file, "utf-8");
740
+ const name = options.name || path.basename(file, path.extname(file));
741
+
742
+ // Extract metadata from header comments
743
+ const metadata = {};
744
+ const metaRegex = /\/\/\s*@(\w+)\s+(.*)/g;
745
+ let match;
746
+ while ((match = metaRegex.exec(code)) !== null) {
747
+ metadata[match[1]] = match[2].trim();
748
+ }
749
+
750
+ const payload = {
751
+ name,
752
+ code,
753
+ trigger: metadata.trigger || options.trigger,
754
+ };
755
+
756
+ const data = await api.pushScript(payload);
757
+
758
+ if (options.json) {
759
+ console.log(JSON.stringify(data, null, 2));
760
+ } else {
761
+ console.log(chalk.green("Script pushed:"));
762
+ console.log(` ID: ${data.script?.id || data.id}`);
763
+ console.log(` Name: ${name}`);
764
+ console.log(` Version: ${data.script?.version || data.version || 1}`);
765
+ }
766
+
767
+ // Auto-create schedule if metadata includes startDateTime
768
+ if (metadata.startDateTime) {
769
+ const eventPayload = {
770
+ ScriptId: data.script?.id || data.id,
771
+ title: metadata.title || name,
772
+ startDateTime: metadata.startDateTime,
773
+ timezone: metadata.tz || metadata.timezone || "UTC",
774
+ };
775
+ if (metadata.rrule) eventPayload.rrule = metadata.rrule;
776
+ if (metadata.endDateTime) eventPayload.endDateTime = metadata.endDateTime;
777
+
778
+ const eventData = await api.createScriptEvent(eventPayload);
779
+ console.log(chalk.green("\nSchedule created:"));
780
+ console.log(` Event ID: ${eventData.scriptEvent?.id || eventData.id}`);
781
+ console.log(` Starts: ${eventPayload.startDateTime}`);
782
+ if (eventPayload.rrule) {
783
+ console.log(` Recurs: ${eventPayload.rrule}`);
784
+ }
785
+ }
786
+ } catch (error) {
787
+ console.error(chalk.red(`Error: ${error.message}`));
788
+ if (error.details) {
789
+ error.details.forEach((d) => console.error(chalk.red(` - ${d}`)));
790
+ }
791
+ process.exit(1);
792
+ }
793
+ });
794
+
795
+ scriptCmd
796
+ .command("list")
797
+ .description("List your scripts")
798
+ .option("-j, --json", "Output as JSON")
799
+ .action(async (options) => {
800
+ try {
801
+ const data = await api.listScripts();
802
+ const scripts = data.scripts || data;
803
+
804
+ if (options.json) {
805
+ console.log(JSON.stringify(data, null, 2));
806
+ } else {
807
+ console.log(chalk.bold("\nScripts:\n"));
808
+ if (!scripts.length) {
809
+ console.log(" No scripts found.");
810
+ } else {
811
+ for (const script of scripts) {
812
+ const statusColor = script.status === "active" ? chalk.green : chalk.gray;
813
+ console.log(` ${statusColor(script.status)} ${script.name} (v${script.version || 1})`);
814
+ console.log(chalk.gray(` ID: ${script.id} Trigger: ${script.trigger}`));
815
+ }
816
+ }
817
+ console.log("");
818
+ }
819
+ } catch (error) {
820
+ console.error(chalk.red(`Error: ${error.message}`));
821
+ process.exit(1);
822
+ }
823
+ });
824
+
825
+ scriptCmd
826
+ .command("delete <id>")
827
+ .description("Deprecate a script (soft delete)")
828
+ .option("-j, --json", "Output as JSON")
829
+ .action(async (id, options) => {
830
+ try {
831
+ const data = await api.deprecateScript(id);
832
+
833
+ if (options.json) {
834
+ console.log(JSON.stringify(data, null, 2));
835
+ } else {
836
+ console.log(chalk.green(`Script deprecated: ${id}`));
837
+ }
838
+ } catch (error) {
839
+ console.error(chalk.red(`Error: ${error.message}`));
840
+ process.exit(1);
841
+ }
842
+ });
843
+
844
+ scriptCmd
845
+ .command("schedule <script-id>")
846
+ .description("Schedule a script as a calendar event")
847
+ .requiredOption("--start <datetime>", "Start date/time (ISO 8601)")
848
+ .option("--end <datetime>", "End date/time (ISO 8601)")
849
+ .option("--rrule <rrule>", "Recurrence rule (RFC 5545 RRULE)")
850
+ .option("--tz <timezone>", "Timezone (e.g., America/New_York)", "UTC")
851
+ .option("-t, --title <title>", "Event title")
852
+ .option("-j, --json", "Output as JSON")
853
+ .action(async (scriptId, options) => {
854
+ try {
855
+ const payload = {
856
+ ScriptId: scriptId,
857
+ title: options.title || `Script ${scriptId}`,
858
+ startDateTime: options.start,
859
+ timezone: options.tz,
860
+ };
861
+ if (options.end) payload.endDateTime = options.end;
862
+ if (options.rrule) payload.rrule = options.rrule;
863
+
864
+ const data = await api.createScriptEvent(payload);
865
+ const event = data.scriptEvent || data;
866
+
867
+ if (options.json) {
868
+ console.log(JSON.stringify(data, null, 2));
869
+ } else {
870
+ console.log(chalk.green("Script scheduled:"));
871
+ console.log(` Event ID: ${event.id}`);
872
+ console.log(` Title: ${event.title}`);
873
+ console.log(` Starts: ${event.startDateTime}`);
874
+ console.log(` Timezone: ${event.timezone}`);
875
+ if (event.rrule) {
876
+ console.log(` Recurs: ${event.rrule}`);
877
+ }
878
+ if (event.nextRunAt) {
879
+ console.log(` Next run: ${event.nextRunAt}`);
880
+ }
881
+ }
882
+ } catch (error) {
883
+ console.error(chalk.red(`Error: ${error.message}`));
884
+ if (error.details) {
885
+ error.details.forEach((d) => console.error(chalk.red(` - ${d}`)));
886
+ }
887
+ process.exit(1);
888
+ }
889
+ });
890
+
891
+ scriptCmd
892
+ .command("events")
893
+ .description("List scheduled script events")
894
+ .option("--start <date>", "Filter by start date (ISO 8601)")
895
+ .option("--end <date>", "Filter by end date (ISO 8601)")
896
+ .option("-j, --json", "Output as JSON")
897
+ .action(async (options) => {
898
+ try {
899
+ const params = {};
900
+ if (options.start) params.start = options.start;
901
+ if (options.end) params.end = options.end;
902
+
903
+ const data = await api.listScriptEvents(params);
904
+ const events = data.scriptEvents || data;
905
+
906
+ if (options.json) {
907
+ console.log(JSON.stringify(data, null, 2));
908
+ } else {
909
+ console.log(chalk.bold("\nScheduled Script Events:\n"));
910
+ if (!events.length) {
911
+ console.log(" No scheduled events found.");
912
+ } else {
913
+ for (const event of events) {
914
+ const statusColor =
915
+ event.status === "active" ? chalk.green :
916
+ event.status === "paused" ? chalk.yellow :
917
+ chalk.gray;
918
+ console.log(` ${statusColor(event.status)} ${event.title}`);
919
+ console.log(chalk.gray(` ID: ${event.id}`));
920
+ console.log(chalk.gray(` Start: ${event.startDateTime} TZ: ${event.timezone}`));
921
+ if (event.rrule) {
922
+ console.log(chalk.gray(` Recurs: ${event.rrule}`));
923
+ }
924
+ if (event.nextRunAt) {
925
+ console.log(chalk.gray(` Next run: ${event.nextRunAt}`));
926
+ }
927
+ if (event.Script) {
928
+ console.log(chalk.gray(` Script: ${event.Script.name} (${event.Script.id})`));
929
+ }
930
+ }
931
+ }
932
+ console.log("");
933
+ }
934
+ } catch (error) {
935
+ console.error(chalk.red(`Error: ${error.message}`));
936
+ process.exit(1);
937
+ }
938
+ });
939
+
940
+ scriptCmd
941
+ .command("pause <event-id>")
942
+ .description("Pause a scheduled script event")
943
+ .option("-j, --json", "Output as JSON")
944
+ .action(async (eventId, options) => {
945
+ try {
946
+ const data = await api.pauseScriptEvent(eventId);
947
+
948
+ if (options.json) {
949
+ console.log(JSON.stringify(data, null, 2));
950
+ } else {
951
+ console.log(chalk.green(`Script event paused: ${eventId}`));
952
+ }
953
+ } catch (error) {
954
+ console.error(chalk.red(`Error: ${error.message}`));
955
+ process.exit(1);
956
+ }
957
+ });
958
+
959
+ scriptCmd
960
+ .command("resume <event-id>")
961
+ .description("Resume a paused script event")
962
+ .option("-j, --json", "Output as JSON")
963
+ .action(async (eventId, options) => {
964
+ try {
965
+ const data = await api.resumeScriptEvent(eventId);
966
+
967
+ if (options.json) {
968
+ console.log(JSON.stringify(data, null, 2));
969
+ } else {
970
+ console.log(chalk.green(`Script event resumed: ${eventId}`));
971
+ }
972
+ } catch (error) {
973
+ console.error(chalk.red(`Error: ${error.message}`));
974
+ process.exit(1);
975
+ }
976
+ });
977
+
717
978
  // ============================================================================
718
979
  // Config Commands
719
980
  // ============================================================================