@orqint/cli 0.1.0 → 0.2.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.
- package/README.md +47 -51
- package/dist/orqint/commands/init.d.ts +1 -0
- package/dist/orqint/commands/init.js +9 -0
- package/dist/orqint/commands/sync-assets.d.ts +6 -0
- package/dist/orqint/commands/sync-assets.js +75 -0
- package/dist/orqint/commands/update.js +5 -0
- package/dist/orqint/engine/paths.d.ts +1 -0
- package/dist/orqint/engine/paths.js +4 -0
- package/dist/orqint/engine/user-assets.d.ts +46 -0
- package/dist/orqint/engine/user-assets.js +234 -0
- package/dist/orqint/index.js +17 -1
- package/package.json +4 -2
- package/{AGENTS.md → user-assets/AGENTS.md} +2 -8
- package/user-assets/ORQINT.md +53 -0
- package/user-assets/cursor-rules/orqint-workflow.mdc +18 -0
package/README.md
CHANGED
|
@@ -1,100 +1,96 @@
|
|
|
1
1
|
# Orqint
|
|
2
2
|
|
|
3
|
-
CLI orchestration for Cursor-native `think` / `plan` workflows.
|
|
3
|
+
CLI orchestration for Cursor-native `think` / `plan` workflows (prepare in the CLI → work in Cursor → apply JSON back). Deep product spec: [docs/PRD/PRD.md](docs/PRD/PRD.md).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Use Orqint in your project
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- Node.js (LTS)
|
|
10
|
-
- [pnpm](https://pnpm.io/installation) (for developing this repo)
|
|
11
|
-
- [npm](https://www.npmjs.com/) (ships with Node; used by `npx` and `orqint update`)
|
|
12
|
-
|
|
13
|
-
## Run with `npx` (no global install)
|
|
14
|
-
|
|
15
|
-
After the package is **published to npm** (see below):
|
|
7
|
+
You need [Node.js](https://nodejs.org/) (LTS). From your **project root**:
|
|
16
8
|
|
|
17
9
|
```bash
|
|
18
|
-
npx --yes @orqint/cli@latest version
|
|
19
10
|
npx --yes @orqint/cli@latest init
|
|
20
11
|
```
|
|
21
12
|
|
|
22
|
-
|
|
13
|
+
1. **`init`** creates the `orqint/` layout (`memory.json`, prompts, handoff folder, etc.).
|
|
14
|
+
2. It also installs **user assets** when missing: **`AGENTS.md`**, **`ORQINT.md`**, and **`.cursor/rules/orqint-workflow.mdc`** (so Cursor can help with handoffs). Existing files are **not** overwritten — use **`orqint init --force-user-assets`** only if you want to refresh those from the package.
|
|
15
|
+
3. Open **`ORQINT.md`** in your repo for the short workflow (commands and tables).
|
|
23
16
|
|
|
24
|
-
|
|
25
|
-
npx --yes @orqint/cli@latest update
|
|
26
|
-
```
|
|
17
|
+
### After `npm update @orqint/cli` (safe template refresh)
|
|
27
18
|
|
|
28
|
-
|
|
19
|
+
Orqint records a **lock** at **`orqint/core/user-assets.lock.json`** for each managed file it wrote. **`orqint sync-assets`** replaces those files from the **installed** package only when the file on disk still matches the lock (so your edits are never silently overwritten). It does **not** reset **`memory.json`**.
|
|
29
20
|
|
|
30
21
|
```bash
|
|
31
|
-
|
|
22
|
+
npm update @orqint/cli
|
|
23
|
+
orqint sync-assets
|
|
32
24
|
```
|
|
33
25
|
|
|
34
|
-
`orqint
|
|
35
|
-
|
|
36
|
-
### Publish (maintainers)
|
|
37
|
-
|
|
38
|
-
From this repo, with an npm login that can publish to the `@orqint` scope:
|
|
26
|
+
If your global `orqint` is older than the package you want, run the latest CLI and sync in one step:
|
|
39
27
|
|
|
40
28
|
```bash
|
|
41
|
-
|
|
42
|
-
npm publish
|
|
29
|
+
orqint update --install sync-assets
|
|
43
30
|
```
|
|
44
31
|
|
|
45
|
-
|
|
32
|
+
(Uses `npx @orqint/cli@latest sync-assets`. On Windows, Node runs this with `shell: true` so `npx` resolves like other npm commands.)
|
|
46
33
|
|
|
47
|
-
|
|
34
|
+
- **`orqint sync-assets --dry-run`** — print what would happen.
|
|
35
|
+
- **`orqint sync-assets --migrate`** — one-shot: replace all managed templates from the current package and write the lock (for projects created before the lockfile existed).
|
|
36
|
+
- **`orqint sync-assets --force`** — overwrite all managed files and refresh the lock (same files as **`init --force-user-assets`**, without touching memory).
|
|
48
37
|
|
|
49
|
-
|
|
38
|
+
**Teams:** commit **`orqint/core/user-assets.lock.json`** so everyone shares the same “managed vs customized” baseline. Without it, `sync-assets` stays conservative for files that exist but have no lock entry until someone runs **`--migrate`** once.
|
|
39
|
+
|
|
40
|
+
Other commands (after init):
|
|
50
41
|
|
|
51
42
|
```bash
|
|
52
|
-
|
|
43
|
+
npx --yes @orqint/cli@latest update
|
|
44
|
+
npx --yes @orqint/cli@latest <command>
|
|
53
45
|
```
|
|
54
46
|
|
|
55
|
-
|
|
47
|
+
`orqint init --brownfield` can seed hints from your `package.json` / `README.md`. `orqint init --force` resets **Orqint runtime** (e.g. `memory.json`) and does **not** replace your root `AGENTS.md` / `ORQINT.md` / Cursor rules unless you also pass **`--force-user-assets`**.
|
|
56
48
|
|
|
57
|
-
|
|
49
|
+
---
|
|
58
50
|
|
|
59
|
-
|
|
60
|
-
pnpm run cli:install:pnpm
|
|
61
|
-
```
|
|
51
|
+
## Maintainers — this repository
|
|
62
52
|
|
|
63
|
-
|
|
53
|
+
CLI source: [`packages/cli/`](packages/cli/README.md). Root [`orqint/`](orqint/core/) is **project state** for dogfooding (not TypeScript sources). Shipped **user-facing** files for npm live under **`user-assets/`** (see [user-assets/FOR_MAINTAINERS.md](user-assets/FOR_MAINTAINERS.md)). Layout map: [docs/Repository-layout.md](docs/Repository-layout.md).
|
|
64
54
|
|
|
65
|
-
|
|
55
|
+
### Prerequisites (development)
|
|
66
56
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
57
|
+
- Node.js (LTS)
|
|
58
|
+
- [pnpm](https://pnpm.io/installation)
|
|
59
|
+
- npm (for `npx` and `orqint update`)
|
|
70
60
|
|
|
71
|
-
|
|
61
|
+
### Publish
|
|
72
62
|
|
|
73
63
|
```bash
|
|
74
|
-
|
|
64
|
+
npm run build
|
|
65
|
+
npm publish
|
|
75
66
|
```
|
|
76
67
|
|
|
77
|
-
|
|
68
|
+
Scoped package **`@orqint/cli`**; `publishConfig.access` is `public`.
|
|
69
|
+
|
|
70
|
+
### Install `orqint` globally from this clone
|
|
78
71
|
|
|
79
72
|
```bash
|
|
80
|
-
|
|
73
|
+
pnpm run cli:install
|
|
81
74
|
```
|
|
82
75
|
|
|
83
|
-
|
|
76
|
+
Windows (no bash): `pnpm run cli:install:pnpm`. If `orqint` is not on PATH, see `pnpm bin -g`.
|
|
84
77
|
|
|
85
|
-
|
|
78
|
+
Shell alternative: `bash scripts/setup-cli.sh`
|
|
79
|
+
|
|
80
|
+
### Update after pulling
|
|
86
81
|
|
|
87
82
|
```bash
|
|
88
|
-
pnpm run cli:upgrade
|
|
83
|
+
pnpm run cli:upgrade
|
|
89
84
|
```
|
|
90
85
|
|
|
91
|
-
|
|
86
|
+
Or `pnpm run cli:upgrade:pnpm` without `git pull` inside the script.
|
|
87
|
+
|
|
88
|
+
### Test project (local build)
|
|
92
89
|
|
|
93
|
-
1. Build /
|
|
94
|
-
2.
|
|
95
|
-
3. Run `orqint init`, then follow [AGENTS.md](AGENTS.md) and [docs/orqint-cursor-starter.md](docs/orqint-cursor-starter.md).
|
|
90
|
+
1. Build / link CLI as above.
|
|
91
|
+
2. In a throwaway folder: `orqint init`, then follow **[user-assets/ORQINT.md](user-assets/ORQINT.md)** (same text users get) and [docs/orqint-cursor-starter.md](docs/orqint-cursor-starter.md).
|
|
96
92
|
|
|
97
|
-
|
|
93
|
+
### Run CLI without global install (this repo)
|
|
98
94
|
|
|
99
95
|
```bash
|
|
100
96
|
pnpm run build
|
|
@@ -37,6 +37,7 @@ exports.runInit = runInit;
|
|
|
37
37
|
const path = __importStar(require("path"));
|
|
38
38
|
const fs = __importStar(require("fs-extra"));
|
|
39
39
|
const paths_1 = require("../engine/paths");
|
|
40
|
+
const user_assets_1 = require("../engine/user-assets");
|
|
40
41
|
const memory_1 = require("../utils/memory");
|
|
41
42
|
const logger_1 = require("../utils/logger");
|
|
42
43
|
async function runInit(cwd, opts) {
|
|
@@ -59,6 +60,14 @@ async function runInit(cwd, opts) {
|
|
|
59
60
|
await fs.ensureDir(path.join(root, ".handoff"));
|
|
60
61
|
await fs.ensureDir((0, paths_1.orqintSpecDir)(cwd));
|
|
61
62
|
await fs.ensureDir((0, paths_1.orqintArchiveDir)(cwd));
|
|
63
|
+
const { copiedPaths: userAssetsCopied } = await (0, user_assets_1.applyUserAssetsInit)(cwd, {
|
|
64
|
+
forceUserAssets: !!opts.forceUserAssets,
|
|
65
|
+
});
|
|
66
|
+
if (userAssetsCopied.length > 0) {
|
|
67
|
+
(0, logger_1.log)("info", "init_next_step", {
|
|
68
|
+
hint: "Open ORQINT.md in your project root for the workflow overview.",
|
|
69
|
+
});
|
|
70
|
+
}
|
|
62
71
|
let memory = (0, memory_1.ensureDefaults)(JSON.parse(JSON.stringify(memory_1.EMPTY_MEMORY)));
|
|
63
72
|
if (opts.brownfield) {
|
|
64
73
|
memory = await seedFromRepo(cwd, memory);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.runSyncAssets = runSyncAssets;
|
|
37
|
+
const fs = __importStar(require("fs-extra"));
|
|
38
|
+
const paths_1 = require("../engine/paths");
|
|
39
|
+
const user_assets_1 = require("../engine/user-assets");
|
|
40
|
+
async function runSyncAssets(cwd, opts) {
|
|
41
|
+
const memPath = (0, paths_1.memoryJsonPath)(cwd);
|
|
42
|
+
if (!(await fs.pathExists(memPath))) {
|
|
43
|
+
throw new Error(`No Orqint project here (missing ${memPath}). Run: orqint init`);
|
|
44
|
+
}
|
|
45
|
+
const result = await (0, user_assets_1.applyUserAssetsSync)(cwd, {
|
|
46
|
+
force: opts.force,
|
|
47
|
+
migrate: opts.migrate,
|
|
48
|
+
dryRun: opts.dryRun,
|
|
49
|
+
});
|
|
50
|
+
const byPathWould = new Map(result.wouldDo.map((w) => [w.path, w.action]));
|
|
51
|
+
const skipReason = new Map(result.skippedPaths.map((s) => [s.path, s.reason]));
|
|
52
|
+
const copiedSet = new Set(result.copiedPaths);
|
|
53
|
+
for (const { to: relTo } of user_assets_1.USER_ASSET_MANIFEST) {
|
|
54
|
+
if (opts.dryRun) {
|
|
55
|
+
const action = byPathWould.get(relTo);
|
|
56
|
+
if (action === "copy") {
|
|
57
|
+
console.error(`[dry-run] Would update ${relTo}`);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
const r = skipReason.get(relTo) ?? "no change";
|
|
61
|
+
console.error(`[dry-run] Would leave ${relTo} unchanged (${r})`);
|
|
62
|
+
}
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (copiedSet.has(relTo)) {
|
|
66
|
+
console.error(`Updated ${relTo}`);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
const r = skipReason.get(relTo);
|
|
70
|
+
if (r) {
|
|
71
|
+
console.error(`Left ${relTo} unchanged (${r})`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -35,6 +35,11 @@ function fetchLatestFromNpm(name) {
|
|
|
35
35
|
* Check npm registry for a newer version; optionally re-invoke via npx @latest.
|
|
36
36
|
*/
|
|
37
37
|
function runUpdate(meta, install, forwardArgs) {
|
|
38
|
+
/** Subcommand after `--install` (e.g. `sync-assets`): always run via npx @latest so templates track the published CLI even when this binary already matches npm. */
|
|
39
|
+
if (install && forwardArgs.length > 0) {
|
|
40
|
+
const r = (0, child_process_1.spawnSync)("npx", ["--yes", `${meta.name}@latest`, ...forwardArgs], { stdio: "inherit", shell: process.platform === "win32" });
|
|
41
|
+
process.exit(r.status === null ? 1 : r.status);
|
|
42
|
+
}
|
|
38
43
|
const latest = fetchLatestFromNpm(meta.name);
|
|
39
44
|
if (!latest) {
|
|
40
45
|
console.error(`Could not read latest version from npm for "${meta.name}".`);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export declare function orqintRoot(cwd: string): string;
|
|
2
2
|
export declare function memoryJsonPath(cwd: string): string;
|
|
3
|
+
export declare function userAssetsLockPath(cwd: string): string;
|
|
3
4
|
export declare function handoffDir(cwd: string): string;
|
|
4
5
|
export declare function promptsDir(cwd: string): string;
|
|
5
6
|
export declare function orqintSpecDir(cwd: string): string;
|
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.orqintRoot = orqintRoot;
|
|
37
37
|
exports.memoryJsonPath = memoryJsonPath;
|
|
38
|
+
exports.userAssetsLockPath = userAssetsLockPath;
|
|
38
39
|
exports.handoffDir = handoffDir;
|
|
39
40
|
exports.promptsDir = promptsDir;
|
|
40
41
|
exports.orqintSpecDir = orqintSpecDir;
|
|
@@ -46,6 +47,9 @@ function orqintRoot(cwd) {
|
|
|
46
47
|
function memoryJsonPath(cwd) {
|
|
47
48
|
return path.join(orqintRoot(cwd), "core", "memory.json");
|
|
48
49
|
}
|
|
50
|
+
function userAssetsLockPath(cwd) {
|
|
51
|
+
return path.join(orqintRoot(cwd), "core", "user-assets.lock.json");
|
|
52
|
+
}
|
|
49
53
|
function handoffDir(cwd) {
|
|
50
54
|
return path.join(orqintRoot(cwd), ".handoff");
|
|
51
55
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** Installed package root (contains `user-assets/` next to `dist/`). */
|
|
2
|
+
export declare function packageRootDir(): string;
|
|
3
|
+
/** Paths relative to package root → relative to consumer cwd. */
|
|
4
|
+
export declare const USER_ASSET_MANIFEST: {
|
|
5
|
+
from: string;
|
|
6
|
+
to: string;
|
|
7
|
+
}[];
|
|
8
|
+
export type UserAssetsLockFile = {
|
|
9
|
+
version: number;
|
|
10
|
+
cliVersionWhenWritten: string;
|
|
11
|
+
assets: Record<string, {
|
|
12
|
+
sha256: string;
|
|
13
|
+
}>;
|
|
14
|
+
};
|
|
15
|
+
export declare function lockKeyForDest(relTo: string): string;
|
|
16
|
+
export declare function sha256OfFile(absPath: string): Promise<string>;
|
|
17
|
+
export declare function sha256OfBuffer(buf: Buffer): string;
|
|
18
|
+
export declare function readUserAssetsLock(cwd: string): Promise<UserAssetsLockFile | null>;
|
|
19
|
+
export declare function writeUserAssetsLock(cwd: string, lock: UserAssetsLockFile): Promise<void>;
|
|
20
|
+
export type UserAssetApplyResult = {
|
|
21
|
+
copiedPaths: string[];
|
|
22
|
+
skippedPaths: {
|
|
23
|
+
path: string;
|
|
24
|
+
reason: string;
|
|
25
|
+
}[];
|
|
26
|
+
wouldDo: {
|
|
27
|
+
path: string;
|
|
28
|
+
action: "copy" | "skip";
|
|
29
|
+
}[];
|
|
30
|
+
};
|
|
31
|
+
export type InitAssetOptions = {
|
|
32
|
+
forceUserAssets: boolean;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Init: copy missing assets (or all if forceUserAssets); update lock only for paths written.
|
|
36
|
+
*/
|
|
37
|
+
export declare function applyUserAssetsInit(cwd: string, opts: InitAssetOptions): Promise<UserAssetApplyResult>;
|
|
38
|
+
export type SyncAssetOptions = {
|
|
39
|
+
force: boolean;
|
|
40
|
+
migrate: boolean;
|
|
41
|
+
dryRun: boolean;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Sync: safe update from package when on-disk file still matches lock; migrate/force overwrite all.
|
|
45
|
+
*/
|
|
46
|
+
export declare function applyUserAssetsSync(cwd: string, opts: SyncAssetOptions): Promise<UserAssetApplyResult>;
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.USER_ASSET_MANIFEST = void 0;
|
|
37
|
+
exports.packageRootDir = packageRootDir;
|
|
38
|
+
exports.lockKeyForDest = lockKeyForDest;
|
|
39
|
+
exports.sha256OfFile = sha256OfFile;
|
|
40
|
+
exports.sha256OfBuffer = sha256OfBuffer;
|
|
41
|
+
exports.readUserAssetsLock = readUserAssetsLock;
|
|
42
|
+
exports.writeUserAssetsLock = writeUserAssetsLock;
|
|
43
|
+
exports.applyUserAssetsInit = applyUserAssetsInit;
|
|
44
|
+
exports.applyUserAssetsSync = applyUserAssetsSync;
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const fs = __importStar(require("fs-extra"));
|
|
47
|
+
const crypto_1 = require("crypto");
|
|
48
|
+
const paths_1 = require("./paths");
|
|
49
|
+
const package_meta_1 = require("../package-meta");
|
|
50
|
+
const logger_1 = require("../utils/logger");
|
|
51
|
+
/** Installed package root (contains `user-assets/` next to `dist/`). */
|
|
52
|
+
function packageRootDir() {
|
|
53
|
+
return path.join(__dirname, "..", "..", "..");
|
|
54
|
+
}
|
|
55
|
+
/** Paths relative to package root → relative to consumer cwd. */
|
|
56
|
+
exports.USER_ASSET_MANIFEST = [
|
|
57
|
+
{ from: path.join("user-assets", "AGENTS.md"), to: "AGENTS.md" },
|
|
58
|
+
{ from: path.join("user-assets", "ORQINT.md"), to: "ORQINT.md" },
|
|
59
|
+
{
|
|
60
|
+
from: path.join("user-assets", "cursor-rules", "orqint-workflow.mdc"),
|
|
61
|
+
to: path.join(".cursor", "rules", "orqint-workflow.mdc"),
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
function lockKeyForDest(relTo) {
|
|
65
|
+
return relTo.split(path.sep).join("/");
|
|
66
|
+
}
|
|
67
|
+
function sha256OfFile(absPath) {
|
|
68
|
+
return fs.readFile(absPath).then((buf) => (0, crypto_1.createHash)("sha256").update(buf).digest("hex"));
|
|
69
|
+
}
|
|
70
|
+
function sha256OfBuffer(buf) {
|
|
71
|
+
return (0, crypto_1.createHash)("sha256").update(buf).digest("hex");
|
|
72
|
+
}
|
|
73
|
+
async function readUserAssetsLock(cwd) {
|
|
74
|
+
const p = (0, paths_1.userAssetsLockPath)(cwd);
|
|
75
|
+
if (!(await fs.pathExists(p))) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const raw = await fs.readJson(p);
|
|
80
|
+
if (raw &&
|
|
81
|
+
typeof raw === "object" &&
|
|
82
|
+
typeof raw.version === "number" &&
|
|
83
|
+
raw.assets &&
|
|
84
|
+
typeof raw.assets === "object") {
|
|
85
|
+
return raw;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
(0, logger_1.log)("warn", "user_assets_lock_read_failed", { path: p });
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
async function writeUserAssetsLock(cwd, lock) {
|
|
94
|
+
const p = (0, paths_1.userAssetsLockPath)(cwd);
|
|
95
|
+
await fs.ensureDir(path.dirname(p));
|
|
96
|
+
await fs.writeJson(p, lock, { spaces: 2 });
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Init: copy missing assets (or all if forceUserAssets); update lock only for paths written.
|
|
100
|
+
*/
|
|
101
|
+
async function applyUserAssetsInit(cwd, opts) {
|
|
102
|
+
const pkgRoot = packageRootDir();
|
|
103
|
+
const result = {
|
|
104
|
+
copiedPaths: [],
|
|
105
|
+
skippedPaths: [],
|
|
106
|
+
wouldDo: [],
|
|
107
|
+
};
|
|
108
|
+
const lock = (await readUserAssetsLock(cwd)) ?? {
|
|
109
|
+
version: 1,
|
|
110
|
+
cliVersionWhenWritten: "",
|
|
111
|
+
assets: {},
|
|
112
|
+
};
|
|
113
|
+
const meta = (0, package_meta_1.readPackageMeta)();
|
|
114
|
+
for (const { from: relFrom, to: relTo } of exports.USER_ASSET_MANIFEST) {
|
|
115
|
+
const from = path.join(pkgRoot, relFrom);
|
|
116
|
+
const to = path.join(cwd, relTo);
|
|
117
|
+
const key = lockKeyForDest(relTo);
|
|
118
|
+
if (!(await fs.pathExists(from))) {
|
|
119
|
+
(0, logger_1.log)("warn", "user_asset_bundled_missing", { from });
|
|
120
|
+
result.skippedPaths.push({ path: relTo, reason: "missing in package" });
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const destExists = await fs.pathExists(to);
|
|
124
|
+
const shouldCopy = opts.forceUserAssets || !destExists;
|
|
125
|
+
if (!shouldCopy) {
|
|
126
|
+
(0, logger_1.log)("info", "init_user_asset_skipped_exists", { file: relTo });
|
|
127
|
+
result.skippedPaths.push({ path: relTo, reason: "already exists" });
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
await fs.ensureDir(path.dirname(to));
|
|
131
|
+
await fs.copy(from, to, { overwrite: opts.forceUserAssets });
|
|
132
|
+
const hash = await sha256OfFile(to);
|
|
133
|
+
lock.assets[key] = { sha256: hash };
|
|
134
|
+
lock.cliVersionWhenWritten = meta.version;
|
|
135
|
+
lock.version = 1;
|
|
136
|
+
(0, logger_1.log)("info", "init_user_asset_copied", { file: relTo });
|
|
137
|
+
result.copiedPaths.push(relTo);
|
|
138
|
+
}
|
|
139
|
+
if (result.copiedPaths.length > 0) {
|
|
140
|
+
await writeUserAssetsLock(cwd, lock);
|
|
141
|
+
}
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Sync: safe update from package when on-disk file still matches lock; migrate/force overwrite all.
|
|
146
|
+
*/
|
|
147
|
+
async function applyUserAssetsSync(cwd, opts) {
|
|
148
|
+
const pkgRoot = packageRootDir();
|
|
149
|
+
const result = {
|
|
150
|
+
copiedPaths: [],
|
|
151
|
+
skippedPaths: [],
|
|
152
|
+
wouldDo: [],
|
|
153
|
+
};
|
|
154
|
+
let lock = await readUserAssetsLock(cwd);
|
|
155
|
+
const meta = (0, package_meta_1.readPackageMeta)();
|
|
156
|
+
for (const { from: relFrom, to: relTo } of exports.USER_ASSET_MANIFEST) {
|
|
157
|
+
const from = path.join(pkgRoot, relFrom);
|
|
158
|
+
const to = path.join(cwd, relTo);
|
|
159
|
+
const key = lockKeyForDest(relTo);
|
|
160
|
+
if (!(await fs.pathExists(from))) {
|
|
161
|
+
result.skippedPaths.push({ path: relTo, reason: "missing in package" });
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const bundledBuf = await fs.readFile(from);
|
|
165
|
+
const bundledHash = sha256OfBuffer(bundledBuf);
|
|
166
|
+
const destExists = await fs.pathExists(to);
|
|
167
|
+
let shouldCopy = false;
|
|
168
|
+
let skipReason = "";
|
|
169
|
+
if (opts.force || opts.migrate) {
|
|
170
|
+
shouldCopy = true;
|
|
171
|
+
}
|
|
172
|
+
else if (!destExists) {
|
|
173
|
+
shouldCopy = true;
|
|
174
|
+
}
|
|
175
|
+
else if (!lock || !lock.assets[key]) {
|
|
176
|
+
shouldCopy = false;
|
|
177
|
+
skipReason =
|
|
178
|
+
"file exists but not in lock (run: orqint sync-assets --migrate once, or --force)";
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
const diskHash = await sha256OfFile(to);
|
|
182
|
+
if (diskHash !== lock.assets[key].sha256) {
|
|
183
|
+
shouldCopy = false;
|
|
184
|
+
skipReason = "you edited this file; left unchanged";
|
|
185
|
+
}
|
|
186
|
+
else if (diskHash === bundledHash) {
|
|
187
|
+
shouldCopy = false;
|
|
188
|
+
skipReason = "already up to date";
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
shouldCopy = true;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (opts.dryRun) {
|
|
195
|
+
result.wouldDo.push({
|
|
196
|
+
path: relTo,
|
|
197
|
+
action: shouldCopy ? "copy" : "skip",
|
|
198
|
+
});
|
|
199
|
+
if (!shouldCopy) {
|
|
200
|
+
result.skippedPaths.push({
|
|
201
|
+
path: relTo,
|
|
202
|
+
reason: skipReason || "no change",
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (!shouldCopy) {
|
|
208
|
+
result.skippedPaths.push({
|
|
209
|
+
path: relTo,
|
|
210
|
+
reason: skipReason || "no change",
|
|
211
|
+
});
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
await fs.ensureDir(path.dirname(to));
|
|
215
|
+
await fs.writeFile(to, bundledBuf);
|
|
216
|
+
const newHash = sha256OfBuffer(bundledBuf);
|
|
217
|
+
if (!lock) {
|
|
218
|
+
lock = {
|
|
219
|
+
version: 1,
|
|
220
|
+
cliVersionWhenWritten: meta.version,
|
|
221
|
+
assets: {},
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
lock.assets[key] = { sha256: newHash };
|
|
225
|
+
lock.cliVersionWhenWritten = meta.version;
|
|
226
|
+
lock.version = 1;
|
|
227
|
+
result.copiedPaths.push(relTo);
|
|
228
|
+
(0, logger_1.log)("info", "user_asset_sync_copied", { file: relTo });
|
|
229
|
+
}
|
|
230
|
+
if (!opts.dryRun && lock && result.copiedPaths.length > 0) {
|
|
231
|
+
await writeUserAssetsLock(cwd, lock);
|
|
232
|
+
}
|
|
233
|
+
return result;
|
|
234
|
+
}
|
package/dist/orqint/index.js
CHANGED
|
@@ -40,6 +40,7 @@ const think_1 = require("./commands/think");
|
|
|
40
40
|
const plan_1 = require("./commands/plan");
|
|
41
41
|
const package_meta_1 = require("./package-meta");
|
|
42
42
|
const update_1 = require("./commands/update");
|
|
43
|
+
const sync_assets_1 = require("./commands/sync-assets");
|
|
43
44
|
function printHelp() {
|
|
44
45
|
const { name, version } = (0, package_meta_1.readPackageMeta)();
|
|
45
46
|
console.log(`${name} ${version} — Phase 1 (Cursor-native)
|
|
@@ -49,13 +50,15 @@ No model API keys in this tool: use Cursor on the generated handoff files, then
|
|
|
49
50
|
Commands:
|
|
50
51
|
orqint version | --version | -V
|
|
51
52
|
orqint update [--install] [<args>...] check npm; --install runs npx ${name}@latest <args>
|
|
52
|
-
orqint
|
|
53
|
+
orqint sync-assets [--force] [--dry-run] [--migrate] refresh AGENTS/ORQINT/rules from package when safe
|
|
54
|
+
orqint init [--brownfield] [--migrate-legacy] [--force] [--force-user-assets]
|
|
53
55
|
orqint think prepare "<intent>" [--override key=value ...]
|
|
54
56
|
orqint think apply --file <path>
|
|
55
57
|
orqint plan prepare [--force]
|
|
56
58
|
orqint plan apply --file <path> [--force]
|
|
57
59
|
|
|
58
60
|
Examples:
|
|
61
|
+
orqint update --install sync-assets run latest CLI then sync templates (good after npm update)
|
|
59
62
|
orqint init --brownfield
|
|
60
63
|
orqint think prepare "Build a task dashboard" --override mobile_first=true
|
|
61
64
|
orqint think apply --file orqint/.handoff/think.response.json
|
|
@@ -124,10 +127,23 @@ async function main() {
|
|
|
124
127
|
(0, update_1.runUpdate)((0, package_meta_1.readPackageMeta)(), install, rest);
|
|
125
128
|
return;
|
|
126
129
|
}
|
|
130
|
+
if (cmd === "sync-assets") {
|
|
131
|
+
const args = argv.slice(1);
|
|
132
|
+
const force = takeFlag(args, "--force");
|
|
133
|
+
const migrate = takeFlag(args, "--migrate");
|
|
134
|
+
const dryRun = takeFlag(args, "--dry-run");
|
|
135
|
+
if (args.length > 0) {
|
|
136
|
+
throw new Error(`Unknown arguments: ${args.join(" ")}`);
|
|
137
|
+
}
|
|
138
|
+
await (0, sync_assets_1.runSyncAssets)(cwd, { force, migrate, dryRun });
|
|
139
|
+
process.exit(0);
|
|
140
|
+
}
|
|
127
141
|
if (cmd === "init") {
|
|
128
142
|
const args = argv.slice(1);
|
|
143
|
+
const forceUserAssets = takeFlag(args, "--force-user-assets");
|
|
129
144
|
await (0, init_1.runInit)(cwd, {
|
|
130
145
|
force: takeFlag(args, "--force"),
|
|
146
|
+
forceUserAssets,
|
|
131
147
|
brownfield: takeFlag(args, "--brownfield"),
|
|
132
148
|
migrateLegacy: takeFlag(args, "--migrate-legacy"),
|
|
133
149
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orqint/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "CLI orchestration for Cursor-native think/plan workflows (Orqint)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"orqint",
|
|
@@ -21,7 +21,9 @@
|
|
|
21
21
|
"files": [
|
|
22
22
|
"dist",
|
|
23
23
|
"README.md",
|
|
24
|
-
"AGENTS.md"
|
|
24
|
+
"user-assets/AGENTS.md",
|
|
25
|
+
"user-assets/ORQINT.md",
|
|
26
|
+
"user-assets/cursor-rules/orqint-workflow.mdc"
|
|
25
27
|
],
|
|
26
28
|
"scripts": {
|
|
27
29
|
"prepublishOnly": "npm run build",
|
|
@@ -10,17 +10,11 @@
|
|
|
10
10
|
|
|
11
11
|
- Follow the JSON schema embedded in each handoff.
|
|
12
12
|
- Honor `memory.override` from the sliced context.
|
|
13
|
-
- No OpenAI (or other) API keys in the Orqint CLI; intelligence for `think`/`plan` runs
|
|
13
|
+
- No OpenAI (or other) API keys in the Orqint CLI; intelligence for `think`/`plan` runs in Cursor on the handoff files.
|
|
14
14
|
|
|
15
15
|
## Source of truth
|
|
16
16
|
|
|
17
17
|
- **Canonical:** `orqint/core/memory.json`
|
|
18
18
|
- **Derived:** `orqint-spec/`
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- TypeScript: `packages/cli/src/`
|
|
23
|
-
- Prompt templates to edit: `packages/cli/prompts/` (built into `dist/orqint/prompts/`)
|
|
24
|
-
- **npm `version`:** bump per [docs/PRD/PRD.md](docs/PRD/PRD.md) §17 before each publish; see `.cursor/rules/orqint-versioning.mdc`.
|
|
25
|
-
|
|
26
|
-
See [docs/Repository-layout.md](docs/Repository-layout.md), [docs/PRD/PRD.md](docs/PRD/PRD.md), and `.cursor/rules/orqint-workflow.mdc`.
|
|
20
|
+
See **ORQINT.md** in this repository for commands and `init` options. Package: [@orqint/cli on npm](https://www.npmjs.com/package/@orqint/cli).
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Orqint — Cursor workflow (your project)
|
|
2
|
+
|
|
3
|
+
Orqint drives **prepare → run in Cursor → apply**: the CLI writes handoff files; you run the Agent in Cursor on those files; you save **strict JSON** responses; the CLI merges into `orqint/core/memory.json`. No API keys are required in the CLI.
|
|
4
|
+
|
|
5
|
+
## After `orqint init`
|
|
6
|
+
|
|
7
|
+
- **Canonical state:** `orqint/core/memory.json`
|
|
8
|
+
- **Derived docs:** `orqint-spec/`
|
|
9
|
+
- **Handoffs:** `orqint/.handoff/`
|
|
10
|
+
|
|
11
|
+
Read **`AGENTS.md`** in this repository for the short agent checklist (think/plan flow and rules). If `init` added **`.cursor/rules/orqint-workflow.mdc`**, Cursor can use it when you work in handoff files.
|
|
12
|
+
|
|
13
|
+
## Commands (Phase 1)
|
|
14
|
+
|
|
15
|
+
Use `npx @orqint/cli` or a local/global `orqint` binary the same way.
|
|
16
|
+
|
|
17
|
+
| Step | Command |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| Think handoff | `orqint think prepare "<your intent>"` |
|
|
20
|
+
| In Cursor | Open `orqint/.handoff/think.md`, run the Agent, write valid JSON to e.g. `orqint/.handoff/think.response.json` |
|
|
21
|
+
| Merge think | `orqint think apply --file orqint/.handoff/think.response.json` |
|
|
22
|
+
| Plan handoff | `orqint plan prepare` |
|
|
23
|
+
| In Cursor | Same pattern → `orqint/.handoff/plan.response.json` |
|
|
24
|
+
| Merge plan | `orqint plan apply --file orqint/.handoff/plan.response.json` |
|
|
25
|
+
| Refresh templates after package update | `orqint sync-assets` (see below) |
|
|
26
|
+
|
|
27
|
+
Follow the JSON schema embedded in each handoff file. Honor `memory.override` when the sliced context includes it.
|
|
28
|
+
|
|
29
|
+
## Keeping `AGENTS.md`, `ORQINT.md`, and Cursor rules up to date
|
|
30
|
+
|
|
31
|
+
After **`npm update @orqint/cli`**, run **`orqint sync-assets`**. Orqint updates each managed file **only if** it still matches what Orqint last wrote (tracked in **`orqint/core/user-assets.lock.json`**). If you edited a file, Orqint **leaves it alone** and tells you. **`memory.json`** is not changed.
|
|
32
|
+
|
|
33
|
+
One-liner with the **latest** published CLI (useful if your global `orqint` is old):
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
orqint update --install sync-assets
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- **`--dry-run`** — show actions only.
|
|
40
|
+
- **`--migrate`** — once per old project: replace all managed templates from the package and create/update the full lock.
|
|
41
|
+
- **`--force`** — overwrite every managed file and refresh the lock (power users).
|
|
42
|
+
|
|
43
|
+
Commit **`orqint/core/user-assets.lock.json`** with the repo so teammates share the same baseline.
|
|
44
|
+
|
|
45
|
+
## Optional flags (`init`)
|
|
46
|
+
|
|
47
|
+
- `orqint init --brownfield` — seed `memory.json` hints from `package.json` / `README.md` when present.
|
|
48
|
+
- `orqint init --force` — re-initialize **Orqint runtime** (e.g. `memory.json`, layout). Does **not** overwrite existing **`AGENTS.md`**, **`ORQINT.md`**, or **`.cursor/rules/orqint-workflow.mdc`** in your project.
|
|
49
|
+
- `orqint init --force-user-assets` — refresh those Orqint-shipped files from the package (overwrites the paths above). Use when you want the latest templates from your installed CLI version. Prefer **`sync-assets`** for routine updates after `npm update` (does not reset memory).
|
|
50
|
+
|
|
51
|
+
## Package reference
|
|
52
|
+
|
|
53
|
+
Published CLI: [@orqint/cli on npm](https://www.npmjs.com/package/@orqint/cli).
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Orqint think/plan handoffs — Cursor agent rules (user project)
|
|
3
|
+
globs: orqint/.handoff/**
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Orqint (Phase 1)
|
|
8
|
+
|
|
9
|
+
When the user works with **Orqint handoffs** (`orqint/.handoff/think.md`, `plan.md`):
|
|
10
|
+
|
|
11
|
+
1. **Read** the handoff file they open; it contains sliced memory, the prompt template, and the **exact JSON schema** for your reply.
|
|
12
|
+
2. **Output only JSON** matching that schema (no markdown fences unless they ask to save to `.json` — the file on disk must be parseable JSON).
|
|
13
|
+
3. **Respect `memory.override`** (and any human overrides in the handoff): they override model defaults (e.g. no_supabase, mobile_first).
|
|
14
|
+
4. **Open questions:** at most **3–5** high-value questions in `open_questions` when confidence is low.
|
|
15
|
+
5. **Do not** invent API keys or ask the user to put secrets in the repo; Orqint does not use CLI-side model APIs.
|
|
16
|
+
6. After you produce JSON, the user runs `orqint think apply --file ...` or `orqint plan apply --file ...` — remind them if they forget.
|
|
17
|
+
|
|
18
|
+
For step-by-step usage, see **ORQINT.md** in the project root.
|