openhome-cli 0.1.13 → 0.1.15

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/dist/cli.js CHANGED
@@ -855,11 +855,99 @@ async function resolveAbilityDir(pathArg) {
855
855
  return resolve(pathInput.trim());
856
856
  }
857
857
  async function deployCommand(pathArg, opts = {}) {
858
- p.intro("\u{1F680} Deploy ability");
858
+ p.intro("\u{1F680} Upload Ability");
859
859
  if (pathArg && pathArg.endsWith(".zip") && existsSync2(resolve(pathArg))) {
860
860
  await deployZip(resolve(pathArg), opts);
861
861
  return;
862
862
  }
863
+ if (!pathArg) {
864
+ const mode = await p.select({
865
+ message: "What do you want to upload?",
866
+ options: [
867
+ {
868
+ value: "zip",
869
+ label: "\u{1F4E6} Upload a zip file",
870
+ hint: "I already have a .zip ready"
871
+ },
872
+ {
873
+ value: "folder",
874
+ label: "\u{1F4C1} Upload from a folder",
875
+ hint: "Point me to an ability directory"
876
+ }
877
+ ]
878
+ });
879
+ handleCancel(mode);
880
+ if (mode === "zip") {
881
+ const home = homedir();
882
+ const scanDirs = [
883
+ process.cwd(),
884
+ join2(home, "Desktop"),
885
+ join2(home, "Downloads"),
886
+ join2(home, "Documents")
887
+ ];
888
+ const foundZips = [];
889
+ for (const dir of scanDirs) {
890
+ if (!existsSync2(dir)) continue;
891
+ try {
892
+ for (const file of readdirSync2(dir)) {
893
+ if (file.endsWith(".zip")) {
894
+ const full = join2(dir, file);
895
+ const shortDir = dir.startsWith(home) ? `~${dir.slice(home.length)}` : dir;
896
+ foundZips.push({ path: full, label: `${file} (${shortDir})` });
897
+ }
898
+ }
899
+ } catch {
900
+ }
901
+ }
902
+ let zipPath;
903
+ if (foundZips.length > 0) {
904
+ const zipOptions = [
905
+ ...foundZips.map((z) => ({ value: z.path, label: z.label })),
906
+ {
907
+ value: "__custom__",
908
+ label: "Other...",
909
+ hint: "Enter a path manually"
910
+ }
911
+ ];
912
+ const selected = await p.select({
913
+ message: "Select your zip file",
914
+ options: zipOptions
915
+ });
916
+ handleCancel(selected);
917
+ if (selected === "__custom__") {
918
+ const zipInput = await p.text({
919
+ message: "Path to your zip file",
920
+ placeholder: "~/path/to/ability.zip",
921
+ validate: (val) => {
922
+ if (!val || !val.trim()) return "Path is required";
923
+ if (!existsSync2(resolve(val.trim())))
924
+ return `File not found: ${val.trim()}`;
925
+ if (!val.trim().endsWith(".zip")) return "Must be a .zip file";
926
+ }
927
+ });
928
+ handleCancel(zipInput);
929
+ zipPath = resolve(zipInput.trim());
930
+ } else {
931
+ zipPath = selected;
932
+ }
933
+ } else {
934
+ const zipInput = await p.text({
935
+ message: "Path to your zip file",
936
+ placeholder: "~/Downloads/my-ability.zip",
937
+ validate: (val) => {
938
+ if (!val || !val.trim()) return "Path is required";
939
+ if (!existsSync2(resolve(val.trim())))
940
+ return `File not found: ${val.trim()}`;
941
+ if (!val.trim().endsWith(".zip")) return "Must be a .zip file";
942
+ }
943
+ });
944
+ handleCancel(zipInput);
945
+ zipPath = resolve(zipInput.trim());
946
+ }
947
+ await deployZip(zipPath, opts);
948
+ return;
949
+ }
950
+ }
863
951
  const targetDir = await resolveAbilityDir(pathArg);
864
952
  const s = p.spinner();
865
953
  s.start("Validating ability...");
@@ -3418,10 +3506,15 @@ async function interactiveMenu() {
3418
3506
  const choice = await p.select({
3419
3507
  message: "What would you like to do?",
3420
3508
  options: [
3509
+ {
3510
+ value: "deploy",
3511
+ label: "\u2B06\uFE0F Upload Ability",
3512
+ hint: "Upload a zip file to OpenHome"
3513
+ },
3421
3514
  {
3422
3515
  value: "init",
3423
- label: "\u2728 Create Ability",
3424
- hint: "Scaffold and deploy a new ability"
3516
+ label: "\u2728 Scaffold Ability",
3517
+ hint: "Generate a new ability from a template"
3425
3518
  },
3426
3519
  {
3427
3520
  value: "list",
@@ -3468,6 +3561,9 @@ async function interactiveMenu() {
3468
3561
  });
3469
3562
  handleCancel(choice);
3470
3563
  switch (choice) {
3564
+ case "deploy":
3565
+ await deployCommand();
3566
+ break;
3471
3567
  case "init":
3472
3568
  await initCommand();
3473
3569
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openhome-cli",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "CLI for managing OpenHome voice AI abilities",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.ts CHANGED
@@ -108,10 +108,15 @@ async function interactiveMenu(): Promise<void> {
108
108
  const choice = await p.select({
109
109
  message: "What would you like to do?",
110
110
  options: [
111
+ {
112
+ value: "deploy",
113
+ label: "⬆️ Upload Ability",
114
+ hint: "Upload a zip file to OpenHome",
115
+ },
111
116
  {
112
117
  value: "init",
113
- label: "✨ Create Ability",
114
- hint: "Scaffold and deploy a new ability",
118
+ label: "✨ Scaffold Ability",
119
+ hint: "Generate a new ability from a template",
115
120
  },
116
121
  {
117
122
  value: "list",
@@ -159,6 +164,9 @@ async function interactiveMenu(): Promise<void> {
159
164
  handleCancel(choice);
160
165
 
161
166
  switch (choice) {
167
+ case "deploy":
168
+ await deployCommand();
169
+ break;
162
170
  case "init":
163
171
  await initCommand();
164
172
  break;
@@ -120,14 +120,113 @@ export async function deployCommand(
120
120
  pathArg?: string,
121
121
  opts: { dryRun?: boolean; mock?: boolean; personality?: string } = {},
122
122
  ): Promise<void> {
123
- p.intro("🚀 Deploy ability");
123
+ p.intro("🚀 Upload Ability");
124
124
 
125
- // Direct zip upload path
125
+ // Explicit zip file passed
126
126
  if (pathArg && pathArg.endsWith(".zip") && existsSync(resolve(pathArg))) {
127
127
  await deployZip(resolve(pathArg), opts);
128
128
  return;
129
129
  }
130
130
 
131
+ // No arg — ask whether they have a zip or a folder
132
+ if (!pathArg) {
133
+ const mode = await p.select({
134
+ message: "What do you want to upload?",
135
+ options: [
136
+ {
137
+ value: "zip",
138
+ label: "📦 Upload a zip file",
139
+ hint: "I already have a .zip ready",
140
+ },
141
+ {
142
+ value: "folder",
143
+ label: "📁 Upload from a folder",
144
+ hint: "Point me to an ability directory",
145
+ },
146
+ ],
147
+ });
148
+ handleCancel(mode);
149
+
150
+ if (mode === "zip") {
151
+ const home = homedir();
152
+ const scanDirs = [
153
+ process.cwd(),
154
+ join(home, "Desktop"),
155
+ join(home, "Downloads"),
156
+ join(home, "Documents"),
157
+ ];
158
+
159
+ const foundZips: { path: string; label: string }[] = [];
160
+ for (const dir of scanDirs) {
161
+ if (!existsSync(dir)) continue;
162
+ try {
163
+ for (const file of readdirSync(dir)) {
164
+ if (file.endsWith(".zip")) {
165
+ const full = join(dir, file);
166
+ const shortDir = dir.startsWith(home)
167
+ ? `~${dir.slice(home.length)}`
168
+ : dir;
169
+ foundZips.push({ path: full, label: `${file} (${shortDir})` });
170
+ }
171
+ }
172
+ } catch {
173
+ // skip unreadable dirs
174
+ }
175
+ }
176
+
177
+ let zipPath: string;
178
+
179
+ if (foundZips.length > 0) {
180
+ const zipOptions = [
181
+ ...foundZips.map((z) => ({ value: z.path, label: z.label })),
182
+ {
183
+ value: "__custom__",
184
+ label: "Other...",
185
+ hint: "Enter a path manually",
186
+ },
187
+ ];
188
+ const selected = await p.select({
189
+ message: "Select your zip file",
190
+ options: zipOptions,
191
+ });
192
+ handleCancel(selected);
193
+
194
+ if (selected === "__custom__") {
195
+ const zipInput = await p.text({
196
+ message: "Path to your zip file",
197
+ placeholder: "~/path/to/ability.zip",
198
+ validate: (val) => {
199
+ if (!val || !val.trim()) return "Path is required";
200
+ if (!existsSync(resolve(val.trim())))
201
+ return `File not found: ${val.trim()}`;
202
+ if (!val.trim().endsWith(".zip")) return "Must be a .zip file";
203
+ },
204
+ });
205
+ handleCancel(zipInput);
206
+ zipPath = resolve((zipInput as string).trim());
207
+ } else {
208
+ zipPath = selected as string;
209
+ }
210
+ } else {
211
+ const zipInput = await p.text({
212
+ message: "Path to your zip file",
213
+ placeholder: "~/Downloads/my-ability.zip",
214
+ validate: (val) => {
215
+ if (!val || !val.trim()) return "Path is required";
216
+ if (!existsSync(resolve(val.trim())))
217
+ return `File not found: ${val.trim()}`;
218
+ if (!val.trim().endsWith(".zip")) return "Must be a .zip file";
219
+ },
220
+ });
221
+ handleCancel(zipInput);
222
+ zipPath = resolve((zipInput as string).trim());
223
+ }
224
+
225
+ await deployZip(zipPath, opts);
226
+ return;
227
+ }
228
+ }
229
+
131
230
  const targetDir = await resolveAbilityDir(pathArg);
132
231
 
133
232
  // Step 1: Validate