Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 32 additions & 1 deletion packages/gittensory-miner/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ Current scope is intentionally small:
- workspace package wiring
- CLI entry point
- `--help` and `version` commands
- laptop-mode `init` and `doctor` commands
- startup npm version nudge (override with `--no-update-check` or `GITTENSORY_MINER_NO_UPDATE_CHECK=1`)

Real miner commands land in follow-up issues.
Discovery, planning, create, and manage commands land in follow-up issues.

The package also includes the first metadata-only discovery primitive: `fetchCandidateIssues` lists open issue
metadata across target repos, and `searchCandidateIssues` does the same from a GitHub issue-search query. Both
Expand All @@ -36,8 +37,38 @@ gittensory-miner --help
gittensory-miner help
gittensory-miner --version
gittensory-miner version
gittensory-miner init
gittensory-miner doctor
```

## Laptop Mode

Laptop mode is the zero-infra install-and-run path for local miner state. It uses a local SQLite state file and does not require Docker, Redis, or Postgres.

Laptop mode requires Node.js 22.5.0 or newer because it uses Node's built-in SQLite module.

```sh
npm install -g @jsonbored/gittensory-miner@latest
gittensory-miner init
gittensory-miner doctor
```

`gittensory-miner init` creates the config directory and initializes the SQLite state database. By default the config directory is:

```sh
~/.config/gittensory-miner
```

Path resolution follows this order:

1. `GITTENSORY_MINER_CONFIG_DIR`
2. `XDG_CONFIG_HOME/gittensory-miner`
3. `~/.config/gittensory-miner`

The state database is stored at `state.sqlite3` inside the config directory. Re-running `init` is safe and does not delete existing state.

`gittensory-miner doctor` reports the Node version, config directory, SQLite state path, state file existence/writability, and Docker presence. Docker is informational only and is never required for laptop mode.

## Version check

On every invocation the CLI starts an async npm registry lookup (5s timeout). When the installed package is behind `@jsonbored/gittensory-miner@latest`, it prints a one-line upgrade command to stderr without blocking or failing the requested command. Set `GITTENSORY_NPM_REGISTRY_URL` to point at a mirror, same as `@jsonbored/gittensory-mcp`.
2 changes: 1 addition & 1 deletion packages/gittensory-miner/bin/gittensory-miner.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,6 @@ if (
process.exit(0);
}

const exitCode = runCli(cliArgs, { packageName });
const exitCode = await runCli(cliArgs, { packageName, env: process.env });
await awaitOpportunisticUpdateCheck(updateCheck);
process.exit(exitCode);
13 changes: 12 additions & 1 deletion packages/gittensory-miner/lib/cli.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
import type { MinerEnv, SpawnSyncLike } from "./laptop-init.js";

export function printVersion(input: { packageName: string; packageVersion: string }): void;
export function printHelp(input: { packageName: string }): void;
export function runCli(cliArgs: string[], input: { packageName: string }): number;
export function runCli(
cliArgs: string[],
input: {
packageName: string;
env?: MinerEnv;
homeDir?: string;
nodeVersion?: string;
spawnSync?: SpawnSyncLike;
},
): Promise<number>;
41 changes: 40 additions & 1 deletion packages/gittensory-miner/lib/cli.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
import {
formatInitResult,
formatLaptopDoctor,
initLaptopMode,
inspectLaptopDoctor,
} from "./laptop-init.js";

function formatCliError(error) {
return error instanceof Error ? error.message : String(error);
}

export function printVersion(input) {
console.log(`${input.packageName}/${input.packageVersion} (node ${process.version})`);
}
Expand All @@ -14,15 +25,43 @@ export function printHelp(input) {
" gittensory-miner --version",
" gittensory-miner help",
" gittensory-miner version",
" gittensory-miner init",
" gittensory-miner doctor",
"",
"Options:",
" --no-update-check Skip the npm registry version nudge (also GITTENSORY_MINER_NO_UPDATE_CHECK=1)",
].join("\n"),
);
}

export function runCli(cliArgs, input) {
export async function runCli(cliArgs, input) {
const command = cliArgs[0] ?? "";

if (command === "init") {
try {
const result = await initLaptopMode({
env: input.env,
homeDir: input.homeDir,
});
console.log(formatInitResult(result));
return 0;
} catch (error) {
console.error(`init failed: ${formatCliError(error)}`);
return 1;
}
}

if (command === "doctor") {
const report = await inspectLaptopDoctor({
env: input.env,
homeDir: input.homeDir,
nodeVersion: input.nodeVersion,
spawnSync: input.spawnSync,
});
console.log(formatLaptopDoctor(report));
return 0;
}

console.error(`Unknown command: ${command}. Run ${input.packageName} --help.`);
return 1;
}
62 changes: 62 additions & 0 deletions packages/gittensory-miner/lib/laptop-init.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import type { SpawnSyncReturns } from "node:child_process";

export type MinerEnv = Readonly<Partial<Record<string, string | undefined>>>;

export type SpawnSyncLike = (
command: string,
args: readonly string[],
options: { encoding: "utf8"; timeout: number },
) => SpawnSyncReturns<string>;

export interface LaptopPathOptions {
homeDir?: string;
}

export interface LaptopPaths {
configDir: string;
statePath: string;
}

export interface LaptopInitOptions extends LaptopPathOptions {
env?: MinerEnv;
}

export interface LaptopInitResult extends LaptopPaths {
createdConfigDir: boolean;
createdStateFile: boolean;
}

export interface LaptopDoctorOptions extends LaptopInitOptions {
nodeVersion?: string;
spawnSync?: SpawnSyncLike;
}

export interface LaptopPathReport {
path: string;
exists: boolean;
isDirectory: boolean;
isFile: boolean;
writable: boolean;
error?: string;
}

export interface LaptopDockerReport {
present: boolean;
status: "present" | "absent" | "unavailable";
detail: string;
}

export interface LaptopDoctorReport {
nodeVersion: string;
configDir: LaptopPathReport;
stateFile: LaptopPathReport;
docker: LaptopDockerReport;
}

export function resolveLaptopPaths(env?: MinerEnv, options?: LaptopPathOptions): LaptopPaths;
export function ensureLaptopStateDatabase(statePath: string): Promise<void>;
export function initLaptopMode(options?: LaptopInitOptions): Promise<LaptopInitResult>;
export function inspectLaptopDoctor(options?: LaptopDoctorOptions): Promise<LaptopDoctorReport>;
export function formatInitResult(result: LaptopInitResult): string;
export function formatLaptopDoctor(report: LaptopDoctorReport): string;

Loading
Loading