taskdex 0.1.2 → 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.
package/README.md CHANGED
@@ -36,7 +36,9 @@ taskdex init
36
36
 
37
37
  This will clone the repo and immediately run interactive terminal setup.
38
38
  Setup now defaults to `dev-client` runtime, can build the native app (`expo run:ios` / `expo run:android`), and then starts Expo in `--dev-client` mode.
39
+ For iOS builds, setup asks whether to run on a real iPhone or Simulator, and supports choosing a specific iPhone device name.
39
40
  When a `pnpm-lock.yaml` is present in `mobile`, the CLI installs mobile dependencies with pnpm automatically.
41
+ Setup writes runtime bridge env values into `mobile/.env.local`, sets `CODEX_CWD` for bridge agent workspace, and can pass `OPENAI_API_KEY` if provided.
40
42
 
41
43
  For an already-cloned repo:
42
44
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskdex",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Terminal-first installer and launcher for Taskdex mobile + bridge",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,7 +2,7 @@
2
2
 
3
3
  import crypto from 'node:crypto';
4
4
  import { spawn } from 'node:child_process';
5
- import { existsSync, readdirSync } from 'node:fs';
5
+ import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
6
6
  import os from 'node:os';
7
7
  import path from 'node:path';
8
8
  import process from 'node:process';
@@ -160,6 +160,13 @@ function parseBuildPlatform(input) {
160
160
  return normalized;
161
161
  }
162
162
 
163
+ function parseIosTarget(input) {
164
+ const normalized = input.trim().toLowerCase() || 'iphone';
165
+ if (['iphone', 'device', 'physical'].includes(normalized)) return 'iphone';
166
+ if (['simulator', 'sim', 'ios-simulator'].includes(normalized)) return 'simulator';
167
+ throw new Error('iOS target must be "iphone" or "simulator".');
168
+ }
169
+
163
170
  function parseInstallChoice(input) {
164
171
  const normalized = input.trim().toLowerCase();
165
172
  if (!normalized || normalized === 'y' || normalized === 'yes') return true;
@@ -167,6 +174,26 @@ function parseInstallChoice(input) {
167
174
  throw new Error('Install choice must be Y or N.');
168
175
  }
169
176
 
177
+ function upsertEnvVars(envFilePath, values) {
178
+ const keys = Object.keys(values);
179
+ const original = existsSync(envFilePath) ? readFileSync(envFilePath, 'utf8') : '';
180
+ const lines = original ? original.split(/\r?\n/) : [];
181
+ const seen = new Set();
182
+ const updated = lines.map((line) => {
183
+ const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=/);
184
+ if (!match) return line;
185
+ const key = match[1];
186
+ if (!keys.includes(key)) return line;
187
+ seen.add(key);
188
+ return `${key}=${values[key]}`;
189
+ });
190
+ for (const key of keys) {
191
+ if (!seen.has(key)) updated.push(`${key}=${values[key]}`);
192
+ }
193
+ const nextContent = `${updated.filter((line) => line !== '').join('\n')}\n`;
194
+ writeFileSync(envFilePath, nextContent, 'utf8');
195
+ }
196
+
170
197
  function wait(ms) {
171
198
  return new Promise((resolve) => setTimeout(resolve, ms));
172
199
  }
@@ -183,12 +210,23 @@ async function readSetupConfig() {
183
210
  const runtime = parseRuntime(runtimeInput);
184
211
  let buildDevClient = false;
185
212
  let buildPlatform = process.platform === 'darwin' ? 'ios' : 'android';
213
+ let iosTarget = 'iphone';
214
+ let iosDeviceName = '';
215
+ const openAiApiKey = (await rl.question('OPENAI_API_KEY (optional, Enter to use current shell/Codex login): ')).trim();
216
+ const workspaceInput = (await rl.question('Agent workspace path [repo root]: ')).trim();
186
217
  if (runtime === 'dev-client') {
187
218
  const buildInput = await rl.question('Build native development client now? [Y/n]: ');
188
219
  buildDevClient = parseInstallChoice(buildInput);
189
220
  if (buildDevClient) {
190
221
  const platformInput = await rl.question(`Build platform (ios/android) [${buildPlatform}]: `);
191
222
  buildPlatform = parseBuildPlatform(platformInput);
223
+ if (buildPlatform === 'ios') {
224
+ const targetInput = await rl.question('iOS target (iphone/simulator) [iphone]: ');
225
+ iosTarget = parseIosTarget(targetInput);
226
+ if (iosTarget === 'iphone') {
227
+ iosDeviceName = (await rl.question('iPhone device name (optional, Enter to choose automatically): ')).trim();
228
+ }
229
+ }
192
230
  }
193
231
  }
194
232
 
@@ -199,6 +237,10 @@ async function readSetupConfig() {
199
237
  runtime,
200
238
  buildDevClient,
201
239
  buildPlatform,
240
+ iosTarget,
241
+ iosDeviceName,
242
+ openAiApiKey,
243
+ workspaceInput,
202
244
  installDeps: parseInstallChoice(installInput),
203
245
  };
204
246
  } finally {
@@ -213,11 +255,34 @@ async function runInteractiveSetup(rootDir) {
213
255
  throw new Error(`Invalid Taskdex repository at ${rootDir}`);
214
256
  }
215
257
 
216
- const { port, apiKey, expoMode, runtime, buildDevClient, buildPlatform, installDeps } = await readSetupConfig();
258
+ const {
259
+ port,
260
+ apiKey,
261
+ expoMode,
262
+ runtime,
263
+ buildDevClient,
264
+ buildPlatform,
265
+ iosTarget,
266
+ iosDeviceName,
267
+ openAiApiKey,
268
+ workspaceInput,
269
+ installDeps,
270
+ } = await readSetupConfig();
217
271
  const bridgeUrl = `ws://${getLocalIPv4()}:${port}`;
272
+ const workspacePath = workspaceInput ? path.resolve(workspaceInput) : rootDir;
273
+
274
+ if (!existsSync(workspacePath)) {
275
+ throw new Error(`Workspace path does not exist: ${workspacePath}`);
276
+ }
277
+
278
+ upsertEnvVars(path.join(mobileDir, '.env.local'), {
279
+ EXPO_PUBLIC_BRIDGE_URL: bridgeUrl,
280
+ EXPO_PUBLIC_BRIDGE_API_KEY: apiKey,
281
+ });
218
282
 
219
283
  console.log(`\nBridge URL: ${bridgeUrl}`);
220
284
  console.log(`Bridge API key: ${apiKey}`);
285
+ console.log(`Agent workspace: ${workspacePath}`);
221
286
  console.log(`Expo mode: ${expoMode}\n`);
222
287
 
223
288
  if (installDeps) {
@@ -237,6 +302,8 @@ async function runInteractiveSetup(rootDir) {
237
302
  ...process.env,
238
303
  PORT: String(port),
239
304
  API_KEY: apiKey,
305
+ CODEX_CWD: workspacePath,
306
+ ...(openAiApiKey ? { OPENAI_API_KEY: openAiApiKey } : {}),
240
307
  },
241
308
  });
242
309
 
@@ -255,7 +322,16 @@ async function runInteractiveSetup(rootDir) {
255
322
 
256
323
  if (runtime === 'dev-client' && buildDevClient) {
257
324
  console.log(`\nBuilding native dev client (${buildPlatform})...\n`);
258
- await runCommand(npxCmd, ['expo', `run:${buildPlatform}`, '--no-bundler'], {
325
+ const buildArgs = ['expo', `run:${buildPlatform}`, '--no-bundler'];
326
+ if (buildPlatform === 'ios' && iosTarget === 'iphone') {
327
+ if (iosDeviceName) {
328
+ buildArgs.push('--device', iosDeviceName);
329
+ } else {
330
+ buildArgs.push('--device');
331
+ }
332
+ }
333
+
334
+ await runCommand(npxCmd, buildArgs, {
259
335
  cwd: mobileDir,
260
336
  env: expoEnv,
261
337
  });