@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 CHANGED
@@ -1,100 +1,96 @@
1
1
  # Orqint
2
2
 
3
- CLI orchestration for Cursor-native `think` / `plan` workflows. See [docs/PRD/PRD.md](docs/PRD/PRD.md).
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
- **Maintainers:** CLI source lives under [`packages/cli/`](packages/cli/README.md). Root folder [`orqint/`](orqint/core/) holds **project state** (`memory.json`, etc.) for this repo only, not TypeScript sources. Full map: [docs/Repository-layout.md](docs/Repository-layout.md).
5
+ ## Use Orqint in your project
6
6
 
7
- ## Prerequisites
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
- Check for updates (queries the registry):
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
- ```bash
25
- npx --yes @orqint/cli@latest update
26
- ```
17
+ ### After `npm update @orqint/cli` (safe template refresh)
27
18
 
28
- Run the latest published CLI via npx (recommended when a newer version exists):
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
- npx --yes @orqint/cli@latest <command>
22
+ npm update @orqint/cli
23
+ orqint sync-assets
32
24
  ```
33
25
 
34
- `orqint update --install <args>` re-invokes `npx @orqint/cli@latest <args>` for you when a newer version is available.
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
- npm run build
42
- npm publish
29
+ orqint update --install sync-assets
43
30
  ```
44
31
 
45
- The package is scoped as `@orqint/cli`; `publishConfig.access` is `public` so `npm publish` does not require extra flags.
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
- ## Install the `orqint` command (this machine)
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
- From the **root of this repository**:
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
- pnpm run cli:install
43
+ npx --yes @orqint/cli@latest update
44
+ npx --yes @orqint/cli@latest <command>
53
45
  ```
54
46
 
55
- This installs dependencies, builds `dist/`, and runs `pnpm link --global` so `orqint` is on your PATH.
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
- On Windows (no `bash`), use:
49
+ ---
58
50
 
59
- ```bash
60
- pnpm run cli:install:pnpm
61
- ```
51
+ ## Maintainers — this repository
62
52
 
63
- If `orqint` is not found, add pnpm’s global bin directory to your PATH (run `pnpm bin -g` to see it).
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
- **Shell alternative (same steps):**
55
+ ### Prerequisites (development)
66
56
 
67
- ```bash
68
- bash scripts/setup-cli.sh
69
- ```
57
+ - Node.js (LTS)
58
+ - [pnpm](https://pnpm.io/installation)
59
+ - npm (for `npx` and `orqint update`)
70
60
 
71
- ## Update after pulling changes
61
+ ### Publish
72
62
 
73
63
  ```bash
74
- pnpm run cli:upgrade
64
+ npm run build
65
+ npm publish
75
66
  ```
76
67
 
77
- Or:
68
+ Scoped package **`@orqint/cli`**; `publishConfig.access` is `public`.
69
+
70
+ ### Install `orqint` globally from this clone
78
71
 
79
72
  ```bash
80
- bash scripts/update-cli.sh
73
+ pnpm run cli:install
81
74
  ```
82
75
 
83
- `cli:upgrade` runs `git pull --rebase` (when this folder is a git repo), then reinstalls and rebuilds. Your global link keeps pointing at this folder, so you immediately get the new build.
76
+ Windows (no bash): `pnpm run cli:install:pnpm`. If `orqint` is not on PATH, see `pnpm bin -g`.
84
77
 
85
- After you `git pull` yourself (or without git), you can run:
78
+ Shell alternative: `bash scripts/setup-cli.sh`
79
+
80
+ ### Update after pulling
86
81
 
87
82
  ```bash
88
- pnpm run cli:upgrade:pnpm
83
+ pnpm run cli:upgrade
89
84
  ```
90
85
 
91
- ## Use in a test project
86
+ Or `pnpm run cli:upgrade:pnpm` without `git pull` inside the script.
87
+
88
+ ### Test project (local build)
92
89
 
93
- 1. Build / install CLI as above.
94
- 2. Open an empty folder in Cursor.
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
- ## Use without global install
93
+ ### Run CLI without global install (this repo)
98
94
 
99
95
  ```bash
100
96
  pnpm run build
@@ -1,5 +1,6 @@
1
1
  export type InitOptions = {
2
2
  force?: boolean;
3
+ forceUserAssets?: boolean;
3
4
  brownfield?: boolean;
4
5
  migrateLegacy?: boolean;
5
6
  };
@@ -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,6 @@
1
+ export type SyncAssetsCliOptions = {
2
+ force: boolean;
3
+ migrate: boolean;
4
+ dryRun: boolean;
5
+ };
6
+ export declare function runSyncAssets(cwd: string, opts: SyncAssetsCliOptions): Promise<void>;
@@ -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
+ }
@@ -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 init [--brownfield] [--migrate-legacy] [--force]
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.1.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 here in Cursor.
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
- ## Developing the Orqint CLI (this repository)
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.