@orth/cli 0.2.3 → 0.2.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.
package/README.md CHANGED
@@ -53,13 +53,17 @@ orth api show hunter /v2/domain-search
53
53
 
54
54
  ```bash
55
55
  # GET request with query params
56
- orth api run hunter /v2/domain-search -q domain=stripe.com
56
+ orth run hunter /v2/domain-search -q domain=stripe.com
57
+
58
+ # Multiple query params (two ways)
59
+ orth run searchapi /api/v1/search -q 'engine=amazon_search&q=wireless earbuds'
60
+ orth run searchapi /api/v1/search -q engine=amazon_search -q q=wireless earbuds
57
61
 
58
62
  # POST request with body
59
- orth api run olostep /v1/scrapes --body '{"url": "https://stripe.com"}'
63
+ orth run olostep /v1/scrapes -d '{"url": "https://stripe.com"}'
60
64
 
61
65
  # Raw output for piping
62
- orth api run hunter /v2/domain-search -q domain=stripe.com --raw | jq '.emails'
66
+ orth run hunter /v2/domain-search -q domain=stripe.com --raw | jq '.emails'
63
67
  ```
64
68
 
65
69
  ### Generate Code
@@ -134,6 +138,14 @@ orth skills create owner/repo --path skills/my-skill --ref main
134
138
  orth skills submit ./my-skill
135
139
  orth skills submit --name "My Skill" --tags "react,testing"
136
140
 
141
+ # Update local skill from platform (pull)
142
+ orth skills update owner/my-skill
143
+ orth skills update owner/my-skill ./local-dir --force
144
+
145
+ # Push local changes to platform
146
+ orth skills push owner/my-skill
147
+ orth skills push owner/my-skill ./local-dir
148
+
137
149
  # Request verification (required before discoverability)
138
150
  orth skills request-verification owner/my-skill
139
151
 
@@ -36,7 +36,7 @@ async function apiCommand(slug, path, options) {
36
36
  chalk_1.default.white(api.name || "") +
37
37
  chalk_1.default.gray(` (${api.endpoints?.length || 0} endpoints)`));
38
38
  }
39
- console.log(chalk_1.default.gray("\nRun 'orth api <slug>' to see endpoints for an API"));
39
+ console.log(chalk_1.default.gray("\nRun 'orth api show <slug>' to see endpoints for an API"));
40
40
  return;
41
41
  }
42
42
  if (path) {
@@ -47,11 +47,6 @@ async function apiCommand(slug, path, options) {
47
47
  // Get description from endpoint object if available
48
48
  const desc = data.description || data.endpoint?.description;
49
49
  console.log(chalk_1.default.gray(desc || "No description"));
50
- // Get price
51
- const price = data.price || data.endpoint?.price;
52
- if (price) {
53
- console.log(chalk_1.default.green(`\nPrice: ${typeof price === 'number' ? '$' + price : price}`));
54
- }
55
50
  // Get params from nested endpoint object if needed
56
51
  const queryParams = data.parameters?.query || data.endpoint?.queryParams || [];
57
52
  const bodyParams = data.parameters?.body || data.endpoint?.bodyParams || [];
@@ -133,13 +128,12 @@ async function apiCommand(slug, path, options) {
133
128
  console.log(chalk_1.default.bold(`\n${chalk_1.default.cyan(api.name)} (${api.slug})\n`));
134
129
  for (const endpoint of api.endpoints) {
135
130
  const method = chalk_1.default.yellow(endpoint.method.padEnd(6));
136
- const price = endpoint.price ? chalk_1.default.green(`$${endpoint.price.toFixed(2)}`) : chalk_1.default.gray("free");
137
- console.log(`${method} ${chalk_1.default.white(endpoint.path)} ${price}`);
131
+ console.log(`${method} ${chalk_1.default.white(endpoint.path)}`);
138
132
  if (endpoint.description) {
139
133
  console.log(chalk_1.default.gray(` ${endpoint.description.slice(0, 80)}${endpoint.description.length > 80 ? "..." : ""}`));
140
134
  }
141
135
  }
142
- console.log(chalk_1.default.gray("\nRun 'orth api " + slug + " <path>' for endpoint details"));
136
+ console.log(chalk_1.default.gray("\nRun 'orth api show " + slug + " <path>' for endpoint details"));
143
137
  console.log(chalk_1.default.gray("Run 'orth run " + slug + " <path>' to call an endpoint"));
144
138
  }
145
139
  catch (error) {
@@ -11,12 +11,19 @@ async function runCommand(api, path, options) {
11
11
  const spinner = (0, ora_1.default)(`Calling ${api}${path}...`).start();
12
12
  try {
13
13
  // Parse query params
14
+ // Supports both `-q key=value -q key2=value2` and `-q 'key=value&key2=value2'`
14
15
  const query = {};
15
16
  if (options.query) {
16
17
  for (const param of options.query) {
17
- const [key, value] = param.split("=");
18
- if (key && value !== undefined) {
19
- query[key] = value;
18
+ // Split on & to handle URL-style query strings
19
+ const parts = param.split("&");
20
+ for (const part of parts) {
21
+ const eqIndex = part.indexOf("=");
22
+ if (eqIndex > 0) {
23
+ const key = part.slice(0, eqIndex);
24
+ const value = part.slice(eqIndex + 1);
25
+ query[key] = decodeURIComponent(value);
26
+ }
20
27
  }
21
28
  }
22
29
  }
@@ -61,10 +68,6 @@ async function runCommand(api, path, options) {
61
68
  console.log(JSON.stringify(result.data, null, 2));
62
69
  }
63
70
  else {
64
- // Show cost
65
- if (result.price) {
66
- console.log(chalk_1.default.gray(`Cost: $${result.price}`));
67
- }
68
71
  // Pretty print the response
69
72
  console.log(chalk_1.default.bold("\nResponse:\n"));
70
73
  console.log(JSON.stringify(result.data, null, 2));
@@ -21,12 +21,9 @@ async function searchCommand(query, options) {
21
21
  console.log(chalk_1.default.cyan.bold(`${api.name}`) + chalk_1.default.gray(` (${api.slug})`));
22
22
  for (const endpoint of api.endpoints.slice(0, 3)) {
23
23
  const method = endpoint.method.padEnd(6);
24
- const price = endpoint.price ? chalk_1.default.green(`$${endpoint.price.toFixed(2)}`) : chalk_1.default.gray("free");
25
24
  console.log(chalk_1.default.gray(" ") +
26
25
  chalk_1.default.yellow(method) +
27
- chalk_1.default.white(endpoint.path) +
28
- chalk_1.default.gray(" - ") +
29
- price);
26
+ chalk_1.default.white(endpoint.path));
30
27
  if (endpoint.description) {
31
28
  console.log(chalk_1.default.gray(` ${endpoint.description.slice(0, 80)}${endpoint.description.length > 80 ? "..." : ""}`));
32
29
  }
@@ -21,10 +21,13 @@ export declare function skillsSubmitCommand(inputPath: string | undefined, optio
21
21
  name?: string;
22
22
  tags?: string;
23
23
  }): Promise<void>;
24
- export declare function skillsUpdateCommand(slug: string, inputPath: string | undefined, options: {
24
+ export declare function skillsPushCommand(slug: string, inputPath: string | undefined, options: {
25
25
  name?: string;
26
26
  tags?: string;
27
27
  }): Promise<void>;
28
+ export declare function skillsUpdateCommand(slug: string, inputPath: string | undefined, options: {
29
+ force?: boolean;
30
+ }): Promise<void>;
28
31
  export declare function skillsRequestVerificationCommand(slug: string): Promise<void>;
29
32
  export declare function skillsPublishCommand(slug: string, options: {
30
33
  unpublish?: boolean;
@@ -43,6 +43,7 @@ exports.skillsCreateCommand = skillsCreateCommand;
43
43
  exports.skillsInstallCommand = skillsInstallCommand;
44
44
  exports.skillsInitCommand = skillsInitCommand;
45
45
  exports.skillsSubmitCommand = skillsSubmitCommand;
46
+ exports.skillsPushCommand = skillsPushCommand;
46
47
  exports.skillsUpdateCommand = skillsUpdateCommand;
47
48
  exports.skillsRequestVerificationCommand = skillsRequestVerificationCommand;
48
49
  exports.skillsPublishCommand = skillsPublishCommand;
@@ -577,9 +578,10 @@ async function skillsSubmitCommand(inputPath, options) {
577
578
  }
578
579
  }
579
580
  // ─────────────────────────────────────────────────────────────────────────────
580
- // orth skills update <slug> [path]
581
+ // orth skills push <slug> [path]
582
+ // Push local skill files to remote (replaces remote with local)
581
583
  // ─────────────────────────────────────────────────────────────────────────────
582
- async function skillsUpdateCommand(slug, inputPath, options) {
584
+ async function skillsPushCommand(slug, inputPath, options) {
583
585
  const dirPath = inputPath ? path.resolve(inputPath) : process.cwd();
584
586
  const spinner = (0, ora_1.default)("Reading skill files...").start();
585
587
  try {
@@ -621,7 +623,7 @@ async function skillsUpdateCommand(slug, inputPath, options) {
621
623
  console.error(chalk_1.default.red("Error: Total content too large (max 1MB)"));
622
624
  process.exit(1);
623
625
  }
624
- spinner.text = "Updating skill...";
626
+ spinner.text = "Pushing skill to Orthogonal...";
625
627
  const tags = options.tags
626
628
  ? options.tags.split(",").map((t) => t.trim())
627
629
  : undefined;
@@ -643,7 +645,7 @@ async function skillsUpdateCommand(slug, inputPath, options) {
643
645
  body: updatePayload,
644
646
  });
645
647
  spinner.stop();
646
- console.log(chalk_1.default.green(`\n✓ Skill updated successfully`));
648
+ console.log(chalk_1.default.green(`\n✓ Skill pushed successfully`));
647
649
  console.log(chalk_1.default.bold(`\n${data.skill.name}`));
648
650
  console.log(chalk_1.default.gray(` Slug: ${data.skill.slug}`));
649
651
  console.log(chalk_1.default.gray(` Files: ${files.length}`));
@@ -658,6 +660,71 @@ async function skillsUpdateCommand(slug, inputPath, options) {
658
660
  }
659
661
  }
660
662
  // ─────────────────────────────────────────────────────────────────────────────
663
+ // orth skills update <slug> [path]
664
+ // Pull latest version from Orthogonal and update local files
665
+ // ─────────────────────────────────────────────────────────────────────────────
666
+ async function skillsUpdateCommand(slug, inputPath, options) {
667
+ const spinner = (0, ora_1.default)(`Fetching skill '${slug}' from Orthogonal...`).start();
668
+ try {
669
+ // Fetch skill from API
670
+ const data = await (0, api_js_1.apiRequest)(`/skills/${slug}`);
671
+ const skill = data.skill;
672
+ if (!skill.files || skill.files.length === 0) {
673
+ spinner.stop();
674
+ console.error(chalk_1.default.red("Error: Skill has no files to download"));
675
+ process.exit(1);
676
+ }
677
+ // Determine target directory
678
+ const skillDirName = slug.replace(/\//g, "-");
679
+ const dirPath = inputPath
680
+ ? path.resolve(inputPath)
681
+ : path.join(process.cwd(), skillDirName);
682
+ // Check if directory exists and has content
683
+ if (fs.existsSync(dirPath) && !options.force) {
684
+ const contents = fs.readdirSync(dirPath);
685
+ if (contents.length > 0) {
686
+ spinner.stop();
687
+ console.log(chalk_1.default.yellow(`Directory ${dirPath} already exists with content.`));
688
+ console.log(chalk_1.default.white(`Use ${chalk_1.default.cyan("--force")} to overwrite existing files.`));
689
+ process.exit(1);
690
+ }
691
+ }
692
+ spinner.text = "Writing files...";
693
+ // Create directory
694
+ fs.mkdirSync(dirPath, { recursive: true });
695
+ // Write all files
696
+ for (const file of skill.files) {
697
+ // Sanitize file path to prevent path traversal
698
+ const sanitized = file.filePath.replace(/\.\.\//g, "").replace(/\.\.\\/g, "");
699
+ const filePath = path.resolve(dirPath, sanitized);
700
+ // Ensure resolved path is within dirPath
701
+ if (!filePath.startsWith(path.resolve(dirPath))) {
702
+ console.log(chalk_1.default.yellow(` Skipped unsafe file path: ${file.filePath}`));
703
+ continue;
704
+ }
705
+ const fileDir = path.dirname(filePath);
706
+ fs.mkdirSync(fileDir, { recursive: true });
707
+ fs.writeFileSync(filePath, file.content, "utf-8");
708
+ }
709
+ spinner.stop();
710
+ console.log(chalk_1.default.green(`\n✓ Skill updated from Orthogonal`));
711
+ console.log(chalk_1.default.bold(`\n${skill.name}`));
712
+ console.log(chalk_1.default.gray(` Path: ${dirPath}`));
713
+ console.log(chalk_1.default.gray(` Files: ${skill.files.length}`));
714
+ console.log(chalk_1.default.bold("\nFiles written:"));
715
+ for (const file of skill.files) {
716
+ const primary = file.isPrimary ? chalk_1.default.green(" (primary)") : "";
717
+ console.log(chalk_1.default.gray(" ") + chalk_1.default.white(file.filePath) + primary);
718
+ }
719
+ console.log(chalk_1.default.gray(`\nTo push changes back: ${chalk_1.default.cyan(`orth skills push ${slug} ${dirPath}`)}`));
720
+ }
721
+ catch (error) {
722
+ spinner.stop();
723
+ console.error(chalk_1.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
724
+ process.exit(1);
725
+ }
726
+ }
727
+ // ─────────────────────────────────────────────────────────────────────────────
661
728
  // orth skills request-verification <slug>
662
729
  // ─────────────────────────────────────────────────────────────────────────────
663
730
  async function skillsRequestVerificationCommand(slug) {
package/dist/index.js CHANGED
@@ -177,13 +177,21 @@ skillsGroup
177
177
  }));
178
178
  skillsGroup
179
179
  .command("update <slug> [path]")
180
- .description("Update an existing skill with local files")
181
- .option("-n, --name <name>", "Override skill name from frontmatter")
182
- .option("-t, --tags <tags>", "Comma-separated tags")
180
+ .description("Pull latest skill version from Orthogonal to local directory")
181
+ .option("-f, --force", "Overwrite existing files")
183
182
  .action(asyncAction(async (slug, inputPath, options) => {
184
183
  (0, analytics_js_1.trackEvent)("skills.update", { slug, path: inputPath });
185
184
  await (0, skills_js_1.skillsUpdateCommand)(slug, inputPath, options);
186
185
  }));
186
+ skillsGroup
187
+ .command("push <slug> [path]")
188
+ .description("Push local skill files to Orthogonal (update remote)")
189
+ .option("-n, --name <name>", "Override skill name from frontmatter")
190
+ .option("-t, --tags <tags>", "Comma-separated tags")
191
+ .action(asyncAction(async (slug, inputPath, options) => {
192
+ (0, analytics_js_1.trackEvent)("skills.push", { slug, path: inputPath });
193
+ await (0, skills_js_1.skillsPushCommand)(slug, inputPath, options);
194
+ }));
187
195
  skillsGroup
188
196
  .command("request-verification <slug>")
189
197
  .description("Request verification for your skill (required before discoverability)")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orth/cli",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "CLI to access all APIs and skills on the Orthogonal platform",
5
5
  "main": "dist/index.js",
6
6
  "bin": {