@harnessforce/cli 0.3.0 → 0.4.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/README.md +6 -0
  2. package/dist/bin.js +206 -22
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -31,7 +31,10 @@ harnessforce init
31
31
  | `harnessforce otel-headers` | Prints the `Authorization` header for Claude Code's `otelHeadersHelper`. Exits 3 when there is no key, 4 when the destination differs from the pinned origin, 5 while the CLI version is unsupported, and 1 on other failures. On Linux, after printing a key read from the kernel keyring, it moves that Workspace's items to an unlocked Secret Service (not when the hook starts it), so a reboot does not lose them |
32
32
  | `harnessforce run --issue <identifier> -- <agent> [args]` | Launches an agent with its session linked to an Issue |
33
33
  | `harnessforce import` | Sends the metadata of past Claude Code sessions in connected repositories |
34
+ | `harnessforce status [--json]` | Shows the connected Workspace, your role, the setup steps that remain with their URLs, and whether the `origin` repository of the current directory is connected to the GitHub App. Reads only; the remote URL is compared on this machine and never sent. Exits 0 whenever the status was read |
34
35
  | `harnessforce tune [--all] [--no-send] [--show-report] [--json]` | Analyzes past sessions on this machine by public rules and sends the counts (analysis reports) |
36
+ | `harnessforce tune --report-only [--all]` | Analyzes and sends like `harnessforce tune`, but writes nothing to stdout. Meant for your OS scheduler |
37
+ | `harnessforce tune --print-schedule` | Prints one cron line that runs `harnessforce tune --report-only` every Monday at 9:00 local time. Changes nothing and makes no network request |
35
38
  | `harnessforce tune record` / `harnessforce tune --purge` | Records a proposal from stdin / deletes everything under `~/.harnessforce/tune/` |
36
39
  | `harnessforce top [--once] [--json] [--ascii] [--theme <auto\|light\|dark\|ansi>]` | Shows the Claude Code and Codex sessions on this machine in the terminal: state, tokens, context and tool failures. Reads only local files and never connects to Harnessforce |
37
40
  | `harnessforce hook session-start` | The entry of Codex's `SessionStart` hook: reads `session_id`, `cwd` and `source` from stdin, registers the session, and prints the session ID for the agent's context. Always exits 0 |
@@ -295,6 +298,9 @@ role cannot use it.
295
298
  - `--json` also lists, for each recorded proposal whose application was detected, the value of its
296
299
  category in the 14 days before and after the detection time (`followups`). The difference is not
297
300
  a cause. Once a `--json` run outputs a follow-up whose after period has ended, later runs omit it.
301
+ - To send weekly without opening Claude Code, add the line that `harnessforce tune --print-schedule`
302
+ prints to your crontab yourself. Harnessforce never registers it. To stop, remove the line or set
303
+ `send_report` to `false`.
298
304
  - Everything it keeps is under `~/.harnessforce/tune/`; `harnessforce tune --purge` deletes it.
299
305
 
300
306
  ## License
package/dist/bin.js CHANGED
@@ -1445,6 +1445,38 @@ Exit codes:
1445
1445
 
1446
1446
  Examples:
1447
1447
  harnessforce import
1448
+ `
1449
+ },
1450
+ status: {
1451
+ summary: "Show the Workspace setup steps that remain",
1452
+ text: `Usage: harnessforce status [--json]
1453
+
1454
+ When to use:
1455
+ After init, and any time you want to see which setup steps of the connected
1456
+ Workspace remain and whether this repository is connected to the GitHub App.
1457
+
1458
+ What it does:
1459
+ Reads the setup checklist of the connected Workspace and the list of
1460
+ connected repositories, and prints your role, each step (done, to do with its
1461
+ URL, or "?" when it cannot be read), and whether the repository of the current
1462
+ directory (its origin remote) is connected.
1463
+
1464
+ Options:
1465
+ --json Print the checklist and the repository as one JSON object.
1466
+
1467
+ Sends:
1468
+ Read requests with the API token. It writes nothing.
1469
+
1470
+ Does not send:
1471
+ The URL of the origin remote: it is compared on this machine.
1472
+
1473
+ Exit codes:
1474
+ 0 The status was read, whether or not steps remain.
1475
+ 1 Not logged in, the login expired, or communication failed.
1476
+
1477
+ Examples:
1478
+ harnessforce status
1479
+ harnessforce status --json
1448
1480
  `
1449
1481
  },
1450
1482
  "otel-headers": {
@@ -1509,6 +1541,8 @@ Examples:
1509
1541
  tune: {
1510
1542
  summary: "Analyze your past sessions and report counts",
1511
1543
  text: `Usage: harnessforce tune [--all] [--no-send] [--show-report] [--json]
1544
+ harnessforce tune --report-only [--all]
1545
+ harnessforce tune --print-schedule
1512
1546
  harnessforce tune record
1513
1547
  harnessforce tune --purge
1514
1548
 
@@ -1528,6 +1562,11 @@ Options:
1528
1562
  --show-report Print the exact values that would be sent.
1529
1563
  --json Print the result as one JSON object on stdout. Messages go to
1530
1564
  stderr.
1565
+ --report-only Analyze and send, writing nothing to stdout (for your OS
1566
+ scheduler). Combines with --all only.
1567
+ --print-schedule
1568
+ Print a cron line that runs --report-only every Monday at
1569
+ 9:00. It changes nothing and sends nothing.
1531
1570
  record Read one proposal as JSON from stdin and store it locally.
1532
1571
  --purge Delete the analysis, proposals, and unsent results.
1533
1572
 
@@ -1549,6 +1588,7 @@ Exit codes:
1549
1588
  Examples:
1550
1589
  harnessforce tune
1551
1590
  harnessforce tune --no-send --json
1591
+ harnessforce tune --print-schedule
1552
1592
  harnessforce tune --purge
1553
1593
  `
1554
1594
  },
@@ -1623,6 +1663,7 @@ Examples:
1623
1663
  const ORDER = [
1624
1664
  "init",
1625
1665
  "import",
1666
+ "status",
1626
1667
  "run",
1627
1668
  "tune",
1628
1669
  "top",
@@ -2382,6 +2423,48 @@ async function fetchSessionImportDays(base, apiToken, fetchImpl) {
2382
2423
  value: days
2383
2424
  } : FAILED$1;
2384
2425
  }
2426
+ const ROLES = /* @__PURE__ */ new Set([
2427
+ "owner",
2428
+ "admin",
2429
+ "member",
2430
+ "viewer"
2431
+ ]);
2432
+ const STEP_STATUSES = /* @__PURE__ */ new Set([
2433
+ "done",
2434
+ "todo",
2435
+ "unavailable"
2436
+ ]);
2437
+ function parseSteps(value) {
2438
+ if (!Array.isArray(value)) return void 0;
2439
+ const steps = value.map((item) => {
2440
+ if (typeof item !== "object" || item === null) return void 0;
2441
+ const { step, status, url } = item;
2442
+ return Number.isInteger(step) && step >= 2 && step <= 5 && STEP_STATUSES.has(status) && typeof url === "string" ? {
2443
+ step,
2444
+ status,
2445
+ url
2446
+ } : void 0;
2447
+ });
2448
+ return steps.every((step) => step !== void 0) ? steps : void 0;
2449
+ }
2450
+ async function fetchOnboarding(base, apiToken, fetchImpl) {
2451
+ const outcome = await getJson(underBase(base, "api/v1/onboarding"), apiToken, fetchImpl);
2452
+ if (outcome.kind !== "ok") return outcome;
2453
+ const body = outcome.value;
2454
+ const steps = parseSteps(body.steps);
2455
+ const adminSteps = body.admin_steps === void 0 ? void 0 : parseSteps(body.admin_steps);
2456
+ if (typeof body.workspace_name !== "string" || !ROLES.has(body.role) || typeof body.has_seat !== "boolean" || !steps || body.admin_steps !== void 0 && !adminSteps) return FAILED$1;
2457
+ return {
2458
+ kind: "ok",
2459
+ value: {
2460
+ workspace_name: body.workspace_name,
2461
+ role: body.role,
2462
+ has_seat: body.has_seat,
2463
+ steps,
2464
+ ...adminSteps && { admin_steps: adminSteps }
2465
+ }
2466
+ };
2467
+ }
2385
2468
  // src/import/record-base.ts
2386
2469
  async function readSettingsEnv(path) {
2387
2470
  const read = await readUserSettings(path);
@@ -3254,7 +3337,7 @@ async function scanCodexSessions(options) {
3254
3337
  }
3255
3338
  // src/import/command.ts
3256
3339
  const DAY_MS$1 = 864e5;
3257
- const MESSAGES = {
3340
+ const MESSAGES$1 = {
3258
3341
  runInit: "Run `harnessforce init`",
3259
3342
  invalidUrl: "The connection URL is invalid",
3260
3343
  keychainUnavailable: INIT_MESSAGES.keychainUnavailable,
@@ -3271,15 +3354,15 @@ const limitMessage = {
3271
3354
  monthly_event_limit: (count) => `Could not import ${count} session(s) because the monthly event limit was reached. Run \`harnessforce import\` again after the limit is lifted to resume importing`,
3272
3355
  workspace_read_only: (count) => `Could not import ${count} session(s) because the Workspace is view-only. Run \`harnessforce import\` again after view-only mode is lifted to resume importing`
3273
3356
  };
3274
- const ACCESS_MESSAGES$2 = {
3275
- keychainUnavailable: MESSAGES.keychainUnavailable,
3276
- initRequired: MESSAGES.runInit,
3277
- invalidUrl: MESSAGES.invalidUrl,
3278
- apiTokenMissing: MESSAGES.runInit
3357
+ const ACCESS_MESSAGES$3 = {
3358
+ keychainUnavailable: MESSAGES$1.keychainUnavailable,
3359
+ initRequired: MESSAGES$1.runInit,
3360
+ invalidUrl: MESSAGES$1.invalidUrl,
3361
+ apiTokenMissing: MESSAGES$1.runInit
3279
3362
  };
3280
- function unwrap(outcome) {
3363
+ function unwrap$1(outcome) {
3281
3364
  if (outcome.kind === "ok") return outcome.value;
3282
- return stopWith(outcome.kind === "unauthorized" ? MESSAGES.loginExpired : MESSAGES.readFailed);
3365
+ return stopWith(outcome.kind === "unauthorized" ? MESSAGES$1.loginExpired : MESSAGES$1.readFailed);
3283
3366
  }
3284
3367
  const NOT_IMPORTED_LABELS = [
3285
3368
  ["alreadySent", "already sent"],
@@ -3295,8 +3378,8 @@ function reportNotImported(imported, notes, deps) {
3295
3378
  const line = breakdownLine(notes.breakdown);
3296
3379
  if (line) deps.stdout(`${line}\n`);
3297
3380
  if (imported > 0) return;
3298
- if (notes.breakdown.notConnected > 0) deps.stdout(`${notes.connectedCount === 0 ? MESSAGES.connectNone : MESSAGES.connectOther}\n`);
3299
- else if (!line && !notes.otherNoticeShown) deps.stdout(`${MESSAGES.nothingFound}\n`);
3381
+ if (notes.breakdown.notConnected > 0) deps.stdout(`${notes.connectedCount === 0 ? MESSAGES$1.connectNone : MESSAGES$1.connectOther}\n`);
3382
+ else if (!line && !notes.otherNoticeShown) deps.stdout(`${MESSAGES$1.nothingFound}\n`);
3300
3383
  }
3301
3384
  function report(result, notes, deps) {
3302
3385
  switch (result.kind) {
@@ -3312,10 +3395,10 @@ function report(result, notes, deps) {
3312
3395
  deps.stderr(`${limitMessage[result.reason](result.notImported)}\n`);
3313
3396
  return 1;
3314
3397
  case "unauthorized":
3315
- deps.stderr(`${MESSAGES.revoked}\n`);
3398
+ deps.stderr(`${MESSAGES$1.revoked}\n`);
3316
3399
  return 1;
3317
3400
  case "failed":
3318
- deps.stderr(`${MESSAGES.sendFailed}\n`);
3401
+ deps.stderr(`${MESSAGES$1.sendFailed}\n`);
3319
3402
  return 1;
3320
3403
  }
3321
3404
  }
@@ -3325,7 +3408,7 @@ const sumNotImported = (a, b) => ({
3325
3408
  notConnected: a.notConnected + b.notConnected
3326
3409
  });
3327
3410
  async function runImport(deps) {
3328
- const access = await verifyCliAccess(deps, ACCESS_MESSAGES$2);
3411
+ const access = await verifyCliAccess(deps, ACCESS_MESSAGES$3);
3329
3412
  const destination = {
3330
3413
  workspaceId: access.workspaceId,
3331
3414
  endpoint: withoutExtras(access.ingest)
@@ -3339,8 +3422,8 @@ async function runImport(deps) {
3339
3422
  sleep: deps.sleep,
3340
3423
  homeDir: deps.homeDir
3341
3424
  }).authorizedFetch(deps.fetch);
3342
- const connected = unwrap(await listConnectedRepositories(access.readApiBase, access.accessToken, readFetch));
3343
- const days = unwrap(await fetchSessionImportDays(access.readApiBase, access.accessToken, readFetch));
3425
+ const connected = unwrap$1(await listConnectedRepositories(access.readApiBase, access.accessToken, readFetch));
3426
+ const days = unwrap$1(await fetchSessionImportDays(access.readApiBase, access.accessToken, readFetch));
3344
3427
  const sinceMs = deps.now() - days * DAY_MS$1;
3345
3428
  const claude = await scanSessions({
3346
3429
  projectsDir: join(await recordBase(deps), "projects"),
@@ -3369,7 +3452,7 @@ async function runImport(deps) {
3369
3452
  sessions: unsent,
3370
3453
  fetch: deps.fetch,
3371
3454
  sleep: deps.sleep,
3372
- record: (ids) => recordSentSessions(statePath, destination, ids).catch(() => stopWith(MESSAGES.stateFailed))
3455
+ record: (ids) => recordSentSessions(statePath, destination, ids).catch(() => stopWith(MESSAGES$1.stateFailed))
3373
3456
  }), {
3374
3457
  breakdown: {
3375
3458
  alreadySent: scanned.length - unsent.length,
@@ -10579,14 +10662,14 @@ function candidateList(candidates) {
10579
10662
  const lines = candidates.map(({ identifier, title }) => `${printable(identifier)} ${printable(title)}`);
10580
10663
  return [RUN_MESSAGES.candidatesHeader, ...lines].join("\n");
10581
10664
  }
10582
- const ACCESS_MESSAGES$1 = {
10665
+ const ACCESS_MESSAGES$2 = {
10583
10666
  keychainUnavailable: RUN_MESSAGES.keychainUnavailable,
10584
10667
  initRequired: RUN_MESSAGES.initRequired,
10585
10668
  invalidUrl: RUN_MESSAGES.invalidUrl,
10586
10669
  apiTokenMissing: RUN_MESSAGES.issueInitRequired
10587
10670
  };
10588
10671
  async function prepareLaunch(args, deps) {
10589
- const { workspaceId, ingestEndpoint, readApiBase, accessToken } = await verifyCliAccess(deps, ACCESS_MESSAGES$1);
10672
+ const { workspaceId, ingestEndpoint, readApiBase, accessToken } = await verifyCliAccess(deps, ACCESS_MESSAGES$2);
10590
10673
  if (!isIssueIdentifier(args.issue)) stop("invalidIssue");
10591
10674
  const readFetch = createApiTokenSession({
10592
10675
  keychain: deps.keychain,
@@ -10651,6 +10734,87 @@ async function runWithoutHarnessforce(args, deps) {
10651
10734
  deps.stderr(`${launchFailedMessage(args.agent)}\n`);
10652
10735
  return 1;
10653
10736
  }
10737
+ // src/status/command.ts
10738
+ const STATUS_USAGE = "Usage: harnessforce status [--json]\n";
10739
+ const MESSAGES = {
10740
+ failed: "Could not read the Harnessforce status. Run `harnessforce init` and try again.",
10741
+ loginExpired: "Your login has expired. Run `harnessforce init`"
10742
+ };
10743
+ const ACCESS_MESSAGES$1 = {
10744
+ keychainUnavailable: INIT_MESSAGES.keychainUnavailable,
10745
+ initRequired: MESSAGES.failed,
10746
+ invalidUrl: MESSAGES.failed,
10747
+ apiTokenMissing: MESSAGES.failed
10748
+ };
10749
+ const STEP_TITLES = {
10750
+ 2: "Connect the GitHub App",
10751
+ 3: "Assign repositories to a project",
10752
+ 4: "Set up telemetry",
10753
+ 5: "Wait for your first run"
10754
+ };
10755
+ const STATUS_MARKS = {
10756
+ done: "✓",
10757
+ todo: "・",
10758
+ unavailable: "?"
10759
+ };
10760
+ function unwrap(outcome) {
10761
+ if (outcome.kind === "ok") return outcome.value;
10762
+ return stopWith(outcome.kind === "unauthorized" ? MESSAGES.loginExpired : MESSAGES.failed);
10763
+ }
10764
+ async function originRepository(cwd, git) {
10765
+ const url = await git(cwd, [
10766
+ "remote",
10767
+ "get-url",
10768
+ "origin"
10769
+ ]);
10770
+ return url === void 0 ? void 0 : normalizeRepository(url);
10771
+ }
10772
+ function stepLine(step) {
10773
+ const parts = [STATUS_MARKS[step.status], STEP_TITLES[step.step]];
10774
+ if (step.status === "todo") parts.push(step.url);
10775
+ return parts.join(" ");
10776
+ }
10777
+ async function runStatus(json, deps) {
10778
+ const access = await verifyCliAccess(deps, ACCESS_MESSAGES$1);
10779
+ const readFetch = createApiTokenSession({
10780
+ keychain: deps.keychain,
10781
+ workspaceId: access.workspaceId,
10782
+ readBase: access.readApiBase,
10783
+ fetch: deps.fetch,
10784
+ now: deps.now,
10785
+ sleep: deps.sleep,
10786
+ homeDir: deps.homeDir
10787
+ }).authorizedFetch(deps.fetch);
10788
+ const onboarding = unwrap(await fetchOnboarding(access.readApiBase, access.accessToken, readFetch));
10789
+ const name = await originRepository(deps.cwd, deps.git);
10790
+ const repository = name === void 0 ? null : {
10791
+ name,
10792
+ connected: unwrap(await listConnectedRepositories(access.readApiBase, access.accessToken, readFetch)).has(name)
10793
+ };
10794
+ if (json) {
10795
+ deps.stdout(`${JSON.stringify({
10796
+ ...onboarding,
10797
+ repository
10798
+ })}\n`);
10799
+ return 0;
10800
+ }
10801
+ const lines = [...onboarding.admin_steps ?? [], ...onboarding.steps].sort((a, b) => a.step - b.step).map(stepLine);
10802
+ const repositoryLine = repository && `This repository (${repository.name}) is ${repository.connected ? "connected" : "not connected"} to the GitHub App`;
10803
+ deps.stdout(`${[
10804
+ `${onboarding.workspace_name} (${onboarding.role})`,
10805
+ ...lines,
10806
+ ...repositoryLine ? [repositoryLine] : []
10807
+ ].join("\n")}\n`);
10808
+ return 0;
10809
+ }
10810
+ function statusCommand(args, deps) {
10811
+ const json = args.length === 1 && args[0] === "--json";
10812
+ if (args.length > 0 && !json) {
10813
+ deps.stderr(STATUS_USAGE);
10814
+ return Promise.resolve(1);
10815
+ }
10816
+ return runUntilStop(() => runStatus(json, deps), deps.stderr);
10817
+ }
10654
10818
  // src/top/args.ts
10655
10819
  const TOP_USAGE = "Usage: harnessforce top [--once] [--json] [--ascii] [--theme <auto|light|dark|ansi>]\n";
10656
10820
  const THEMES = [
@@ -12284,7 +12448,7 @@ const TUNE_MESSAGES = {
12284
12448
  unwritable: "Cannot write to `~/.harnessforce/tune/`, so the analysis results are not sent",
12285
12449
  recordFailed: "Could not record the suggestion",
12286
12450
  recordInvalid: (field) => `The suggestion record input is invalid: ${field}`,
12287
- usage: "Usage: harnessforce tune [--all] [--no-send] [--show-report] [--json], harnessforce tune record, or harnessforce tune --purge",
12451
+ usage: "Usage: harnessforce tune [--all] [--no-send] [--show-report] [--json], harnessforce tune --report-only [--all], harnessforce tune --print-schedule, harnessforce tune record, or harnessforce tune --purge",
12288
12452
  purged: "Deleted the analysis results, suggestions and unsent analysis results in `~/.harnessforce/tune/`",
12289
12453
  purgeFailed: "Could not delete some files in `~/.harnessforce/tune/`"
12290
12454
  };
@@ -12870,7 +13034,7 @@ async function analyzeUnderLock(options, access, paths, deps) {
12870
13034
  ]).then(() => true, () => false);
12871
13035
  if (!isWritten) notices.add("unwritable", TUNE_MESSAGES.unwritable, 1);
12872
13036
  if (options.json) deps.stdout(`${asciiJson(output)}\n`);
12873
- else {
13037
+ else if (!options.reportOnly) {
12874
13038
  deps.stdout(renderAnalysis(analyzed.map((a) => a.report), analyzed.map((a) => a.tune.usage), {
12875
13039
  sessionCount: analyzed.length,
12876
13040
  analyzerVersion: ANALYZER_VERSION,
@@ -13110,27 +13274,36 @@ const FLAGS = {
13110
13274
  "--all": "all",
13111
13275
  "--no-send": "noSend",
13112
13276
  "--show-report": "showReport",
13113
- "--json": "json"
13277
+ "--json": "json",
13278
+ "--report-only": "reportOnly"
13114
13279
  };
13280
+ const REPORT_ONLY = "--report-only";
13115
13281
  function parseArgs(args) {
13116
13282
  if (args.length === 1 && args[0] === "record") return { kind: "record" };
13117
13283
  if (args.length === 1 && args[0] === "--purge") return { kind: "purge" };
13284
+ if (args.length === 1 && args[0] === "--print-schedule") return { kind: "printSchedule" };
13118
13285
  const options = {
13119
13286
  all: false,
13120
13287
  noSend: false,
13121
13288
  showReport: false,
13122
- json: false
13289
+ json: false,
13290
+ reportOnly: false
13123
13291
  };
13124
13292
  for (const arg of args) {
13125
13293
  const key = FLAGS[arg];
13126
13294
  if (key === void 0 || options[key]) return void 0;
13127
13295
  options[key] = true;
13128
13296
  }
13297
+ if (options.reportOnly && (options.noSend || options.showReport || options.json)) return void 0;
13129
13298
  return {
13130
13299
  kind: "analyze",
13131
13300
  options
13132
13301
  };
13133
13302
  }
13303
+ function cronCommandPath(path) {
13304
+ return (/^[A-Za-z0-9_./-]+$/.test(path) ? path : `'${path.replaceAll("'", "'\\''")}'`).replaceAll("%", "\\%");
13305
+ }
13306
+ const scheduleLine = (selfPath) => `0 9 * * 1 ${cronCommandPath(selfPath)} tune ${REPORT_ONLY}\n`;
13134
13307
  function canTuneSend(args) {
13135
13308
  const parsed = parseArgs(args);
13136
13309
  return parsed?.kind === "analyze" && !parsed.options.noSend;
@@ -13180,6 +13353,10 @@ async function tuneCommand(args, deps) {
13180
13353
  return 2;
13181
13354
  }
13182
13355
  if (parsed.kind === "record") return record(deps);
13356
+ if (parsed.kind === "printSchedule") {
13357
+ deps.stdout(scheduleLine(deps.selfPath));
13358
+ return 0;
13359
+ }
13183
13360
  if (parsed.kind === "purge") return underLock(deps, TUNE_MESSAGES.purgeFailed, async (paths) => {
13184
13361
  if (!await purgeTuneFiles(paths)) return stopWith(TUNE_MESSAGES.purgeFailed);
13185
13362
  deps.stderr(`${TUNE_MESSAGES.purged}\n`);
@@ -13284,6 +13461,11 @@ async function run(argv, deps) {
13284
13461
  git: cli.importGit,
13285
13462
  now: () => cli.now().getTime()
13286
13463
  }));
13464
+ if (command === "status") return guarded(() => statusCommand(rest, {
13465
+ ...cli,
13466
+ git: cli.importGit,
13467
+ now: () => cli.now().getTime()
13468
+ }));
13287
13469
  if (command === "tune") return guarded(() => tuneCommand(rest, {
13288
13470
  ...cli,
13289
13471
  git: cli.importGit,
@@ -13341,6 +13523,7 @@ const SENDING_COMMANDS = /* @__PURE__ */ new Set([
13341
13523
  "init",
13342
13524
  "run",
13343
13525
  "import",
13526
+ "status",
13344
13527
  "hook"
13345
13528
  ]);
13346
13529
  const isSending = (argv) => SENDING_COMMANDS.has(argv[0] ?? "") || argv[0] === "tune" && canTuneSend(argv.slice(1));
@@ -13543,6 +13726,7 @@ process.exitCode = relaunched ?? await run(argv, {
13543
13726
  devBuild: !isDefault,
13544
13727
  callbackTimeoutMs: CALLBACK_TIMEOUT_MS,
13545
13728
  cwd: process.cwd(),
13729
+ selfPath: process.argv[1],
13546
13730
  git: createGitRunner({
13547
13731
  platform: process.platform,
13548
13732
  env: process.env,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harnessforce/cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Harnessforce command line (harnessforce)",
5
5
  "keywords": [
6
6
  "harnessforce",