omnispace 0.11.0 → 0.12.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
@@ -84,6 +84,13 @@ Tirar esta máquina da conta e o `.omni/` das pastas dela (pede duas confirmaç
84
84
  npx -y omnispace@latest desvincular
85
85
  ```
86
86
 
87
+ Ver e mudar o que o agente pode fazer sozinho com cada ação dos conectores do espaço. Por padrão, leitura é automática, escrita pede confirmação e ação destrutiva fica bloqueada. Restringir vale na hora; liberar pede confirmação no navegador:
88
+
89
+ ```
90
+ npx -y omnispace@latest permissoes
91
+ npx -y omnispace@latest permissoes set slack send_message bloqueado
92
+ ```
93
+
87
94
  No Windows, se o caminho tiver espaço ou apóstrofo, entre na pasta com aspas duplas: `cd "C:\caminho\da\pasta"`.
88
95
 
89
96
  ## O que o Omni lê e envia
package/dist/env.js CHANGED
@@ -11,9 +11,9 @@ const STATUS_TEXT = {
11
11
  error: "com erro",
12
12
  disabled: "desligado",
13
13
  };
14
- const USAGE = `Uso:
15
- omnispace env push [pasta] pergunta chave por chave o que vai para o cofre do espaço
16
- omnispace env list [pasta] mostra as chaves guardadas (só nomes)
14
+ const USAGE = `Uso:
15
+ omnispace env push [pasta] pergunta chave por chave o que vai para o cofre do espaço
16
+ omnispace env list [pasta] mostra as chaves guardadas (só nomes)
17
17
  omnispace env rm <CHAVE>... [--arquivo .env] [--pasta <pasta>] apaga do cofre, de vez`;
18
18
  async function askTerminal(question) {
19
19
  const rl = createInterface({ input: process.stdin, output: process.stdout });
@@ -25,7 +25,7 @@ async function askTerminal(question) {
25
25
  }
26
26
  }
27
27
  /** The space of this folder and this machine's session, or a printed reason why not. */
28
- async function context(root) {
28
+ export async function context(root) {
29
29
  const config = await readSpaceConfig(root);
30
30
  if (!config?.spaceId) {
31
31
  console.error(config ? "O espaço desta pasta ainda não foi criado. Termine a conta no navegador." : "Esta pasta não é um espaço Omni. Rode omnispace init primeiro.");
@@ -44,7 +44,7 @@ async function context(root) {
44
44
  }
45
45
  return { apiUrl: config.apiUrl, token, spaceId: config.spaceId };
46
46
  }
47
- function refused(res, what) {
47
+ export function refused(res, what) {
48
48
  console.error(`${what}: ${res.error}`);
49
49
  if (res.expired || res.status === 401)
50
50
  console.error("Para entrar de novo: omnispace login");
@@ -53,7 +53,7 @@ function refused(res, what) {
53
53
  * Asks for the on-the-spot verification and waits for the browser. Returns the device code to send
54
54
  * with the action, or null (refused, expired, failed).
55
55
  */
56
- async function verify(ctx, action, keys, opts) {
56
+ export async function verify(ctx, action, keys, opts) {
57
57
  const log = opts.log ?? ((line) => console.log(line));
58
58
  const start = await callApi(ctx.apiUrl, ctx.token, "/api/cli/verify/start", { spaceId: ctx.spaceId, action, keys }, { retryDelaysMs: RETRY_DELAYS_MS });
59
59
  if (!start.ok) {
package/dist/include.js CHANGED
@@ -9,6 +9,15 @@ export const SKIP_DIRS = new Set([
9
9
  "node_modules", ".git", ".next", ".vercel", "dist", "build", "out", "coverage",
10
10
  ".turbo", ".cache", "venv", ".venv", "__pycache__", ".idea", ".vscode", "target",
11
11
  ]);
12
+ /**
13
+ * Folders skipped by where they are, not by their name alone. `.claude/worktrees/` holds full copies
14
+ * of a repo that Claude Code sessions work on in parallel (Lucas, 2026-09-28): synced, every copy
15
+ * would go up as text. Matches at any depth (a repo inside the space can have its own).
16
+ */
17
+ const SKIP_DIR_PATHS_RE = /(^|\/)\.claude\/worktrees$/i;
18
+ export function isSkippedDirPath(path) {
19
+ return SKIP_DIR_PATHS_RE.test(path);
20
+ }
12
21
  // Local state of the Omni link itself: machine specific, never part of the space. `.omni/tmp/`
13
22
  // holds downloads of an unfinished pull (fatia 3); `.omni/conflicts.json` and `.omni/backups/`
14
23
  // are the conflict resolution of this machine (fatia 4); `.omni/pull-pending.json` and
@@ -74,7 +83,8 @@ export function isMachineConfig(path) {
74
83
  */
75
84
  export function neverSyncs(path) {
76
85
  const name = path.slice(path.lastIndexOf("/") + 1);
77
- return isSecret(name) || isMachineConfig(path) || isLocalOnly(path);
86
+ const inSkippedDir = /(^|\/)\.claude\/worktrees\//i.test(path);
87
+ return isSecret(name) || isMachineConfig(path) || isLocalOnly(path) || inSkippedDir;
78
88
  }
79
89
  export function isText(name) {
80
90
  const lower = name.toLowerCase();
@@ -143,7 +153,7 @@ export async function buildManifest(root) {
143
153
  const abs = join(dir, entry.name);
144
154
  const relParts = [...parts, entry.name];
145
155
  if (entry.isDirectory()) {
146
- if (SKIP_DIRS.has(entry.name))
156
+ if (SKIP_DIRS.has(entry.name) || isSkippedDirPath(normalizePath(relParts)))
147
157
  continue;
148
158
  // A subfolder with its own .git (repo, submodule or worktree) DOES sync, text only: the
149
159
  // agent needs the code to act on it (find a bug, fix it, report back). Lucas, 2026-09-23,
package/dist/index.js CHANGED
@@ -18,30 +18,32 @@ import { terminalAsk, unlinkMachine } from "./desvincular.js";
18
18
  import { linkExistingSpace } from "./vincular.js";
19
19
  import { askSpace, parseAskArgs } from "./ask.js";
20
20
  import { runEnv } from "./env.js";
21
- const USAGE = `
22
- Omni CLI
23
-
24
- Uso:
25
- omni login entra na sua conta pelo navegador (a sessão vale 14 dias sem uso)
26
- omni logout encerra a sessão da CLI nesta máquina
27
- omni whoami mostra a conta, a máquina e até quando a sessão vale
28
- omni init [pasta] [--nome <nome>] com a CLI logada, cria o espaço na sua conta e liga a pasta
29
- omni init [pasta] --scan-only varre a pasta e mostra o que encontrou
30
- omni init [pasta] --link <código> varre a pasta e envia pro código mostrado no navegador
31
- omni status [pasta] [--resumo] o que mudou aqui e no servidor, e o próximo comando (só lê)
32
- omni ask "<pergunta>" [pasta] responde com base na versão publicada do espaço, com as fontes
33
- [--fontes] só mostra quais arquivos seriam lidos, sem chamar o modelo
34
- [--detalhes] mostra também custo, contexto e arquivos consultados
35
- omni sync [pasta] --push [--yes] publica uma nova versão do espaço no servidor
36
- omni sync [pasta] --pull traz para a pasta a versão mais nova do servidor
37
- [--conflito=local|mesclar|backup] mesma resposta para todos os conflitos, sem perguntar
38
- omni clone <código> <pasta> baixa um espaço da sua conta numa pasta nova desta máquina
39
- omni vincular [pasta] [--espaco <nome>] liga uma pasta que já tem os arquivos a um espaço da conta
40
- omni desvincular [pasta] tira esta máquina da conta e o .omni/ das pastas (pede 2 confirmações)
41
- omni env push|list|rm [pasta] guarda chaves do .env no cofre do espaço, uma a uma (confirma no navegador)
42
-
43
- A CLI não reorganiza a sua pasta. Com --link, o que fazer com o que foi
44
- varrido é decidido na tela de revisão, no navegador.
21
+ import { runPermissoes } from "./permissoes.js";
22
+ const USAGE = `
23
+ Omni CLI
24
+
25
+ Uso:
26
+ omni login entra na sua conta pelo navegador (a sessão vale 14 dias sem uso)
27
+ omni logout encerra a sessão da CLI nesta máquina
28
+ omni whoami mostra a conta, a máquina e até quando a sessão vale
29
+ omni init [pasta] [--nome <nome>] com a CLI logada, cria o espaço na sua conta e liga a pasta
30
+ omni init [pasta] --scan-only varre a pasta e mostra o que encontrou
31
+ omni init [pasta] --link <código> varre a pasta e envia pro código mostrado no navegador
32
+ omni status [pasta] [--resumo] o que mudou aqui e no servidor, e o próximo comando (só lê)
33
+ omni ask "<pergunta>" [pasta] responde com base na versão publicada do espaço, com as fontes
34
+ [--fontes] só mostra quais arquivos seriam lidos, sem chamar o modelo
35
+ [--detalhes] mostra também custo, contexto e arquivos consultados
36
+ omni sync [pasta] --push [--yes] publica uma nova versão do espaço no servidor
37
+ omni sync [pasta] --pull traz para a pasta a versão mais nova do servidor
38
+ [--conflito=local|mesclar|backup] mesma resposta para todos os conflitos, sem perguntar
39
+ omni clone <código> <pasta> baixa um espaço da sua conta numa pasta nova desta máquina
40
+ omni vincular [pasta] [--espaco <nome>] liga uma pasta que já tem os arquivos a um espaço da conta
41
+ omni desvincular [pasta] tira esta máquina da conta e o .omni/ das pastas (pede 2 confirmações)
42
+ omni env push|list|rm [pasta] guarda chaves do .env no cofre do espaço, uma a uma (confirma no navegador)
43
+ omni permissoes [set <conector> <ação> <estado>] [pasta] o que o agente pode fazer sozinho em cada ação
44
+
45
+ A CLI não reorganiza a sua pasta. Com --link, o que fazer com o que foi
46
+ varrido é decidido na tela de revisão, no navegador.
45
47
  `;
46
48
  async function main(argv) {
47
49
  const args = argv.slice(2);
@@ -67,6 +69,8 @@ async function main(argv) {
67
69
  return runClone(args.slice(1));
68
70
  if (command === "env")
69
71
  return runEnv(args.slice(1), { openBrowser: process.stdout.isTTY });
72
+ if (command === "permissoes")
73
+ return runPermissoes(args.slice(1), { openBrowser: process.stdout.isTTY });
70
74
  if (command === "vincular") {
71
75
  const rest = args.slice(1);
72
76
  const espacoIndex = rest.indexOf("--espaco");
@@ -114,20 +118,20 @@ async function main(argv) {
114
118
  if (!who.ok && !who.noToken)
115
119
  console.error(`\n${who.error}`);
116
120
  const site = defaultApiUrl();
117
- console.log(`
118
- Omni CLI
119
-
120
- Já tem conta no Omni? Entre por aqui e rode o init de novo:
121
- npx -y omnispace@latest login
122
-
123
- Primeira vez? Comece pelo navegador:
124
- ${site}/omnispace/escolha
125
-
126
- Lá aparece um código (tipo ABCD-2345). Com ele, dentro da pasta do espaço, rode:
127
- npx -y omnispace@latest init . --link <código>
128
-
129
- Só quer ver o que o Omni leria nesta pasta, sem enviar nada?
130
- npx -y omnispace@latest init . --scan-only
121
+ console.log(`
122
+ Omni CLI
123
+
124
+ Já tem conta no Omni? Entre por aqui e rode o init de novo:
125
+ npx -y omnispace@latest login
126
+
127
+ Primeira vez? Comece pelo navegador:
128
+ ${site}/omnispace/escolha
129
+
130
+ Lá aparece um código (tipo ABCD-2345). Com ele, dentro da pasta do espaço, rode:
131
+ npx -y omnispace@latest init . --link <código>
132
+
133
+ Só quer ver o que o Omni leria nesta pasta, sem enviar nada?
134
+ npx -y omnispace@latest init . --scan-only
131
135
  `);
132
136
  return who.ok || who.noToken ? 0 : 1;
133
137
  }
@@ -0,0 +1,92 @@
1
+ import { resolve } from "node:path";
2
+ import { callApi } from "./api.js";
3
+ import { RETRY_DELAYS_MS } from "./retry.js";
4
+ import { context, refused, verify } from "./env.js";
5
+ // `omnispace permissoes` (docs/plan/permissoes-por-acao.md; Lucas, 2026-09-28): what the agent may
6
+ // do on its own with each connector action of the space. Tightening applies right away; loosening
7
+ // asks for the confirmation in the browser, like a credential.
8
+ const STATES = ["automatico", "confirmar", "bloqueado"];
9
+ const STATE_LABEL = { automatico: "automático", confirmar: "pedir confirmação", bloqueado: "bloqueado" };
10
+ const ORIGIN_LABEL = { padrao: "padrão do Omni", usuario: "definida por você", importacao: "importada" };
11
+ const USAGE = `Uso:
12
+ omnispace permissoes [pasta] lista as ações dos conectores do espaço e o que o agente pode fazer
13
+ omnispace permissoes set <conector> <ação> <estado> [pasta] muda uma ação: automatico, confirmar ou bloqueado
14
+ Liberar (bloqueado para confirmar, ou para automático) pede confirmação no navegador; restringir vale na hora.`;
15
+ export function parseStateArg(value) {
16
+ const plain = (value ?? "").toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "");
17
+ return STATES.find((s) => s === plain) ?? null;
18
+ }
19
+ async function list(root, opts) {
20
+ const log = opts.log ?? ((line) => console.log(line));
21
+ const ctx = await context(root);
22
+ if (!ctx)
23
+ return 1;
24
+ const res = await callApi(ctx.apiUrl, ctx.token, "/api/cli/permissions/list", { spaceId: ctx.spaceId }, { retryDelaysMs: RETRY_DELAYS_MS });
25
+ if (!res.ok) {
26
+ refused(res, "Falha ao ler as permissões");
27
+ return 1;
28
+ }
29
+ const rows = res.permissions;
30
+ if (rows.length === 0) {
31
+ log("Este espaço ainda não tem conector. As permissões aparecem aqui quando houver um (omnispace env push).");
32
+ return 0;
33
+ }
34
+ let current = "";
35
+ for (const r of rows) {
36
+ if (r.connectorId !== current) {
37
+ current = r.connectorId;
38
+ log(`\n${r.connectorName} (${r.connectorId})`);
39
+ }
40
+ log(` ${r.actionKey.padEnd(22)} ${r.actionName.padEnd(26)} ${r.kind.padEnd(10)} ${STATE_LABEL[r.state].padEnd(18)} ${ORIGIN_LABEL[r.origin] ?? r.origin}`);
41
+ }
42
+ log("\nPara mudar: omnispace permissoes set <conector> <ação> <automatico|confirmar|bloqueado>");
43
+ return 0;
44
+ }
45
+ async function set(root, connectorId, actionKey, state, opts) {
46
+ const log = opts.log ?? ((line) => console.log(line));
47
+ const ctx = await context(root);
48
+ if (!ctx)
49
+ return 1;
50
+ const changes = [{ connectorId, actionKey, state }];
51
+ // Not retried: a repeat after a cut connection is harmless (same state), but it is reported.
52
+ let res = await callApi(ctx.apiUrl, ctx.token, "/api/cli/permissions/set", { spaceId: ctx.spaceId, changes });
53
+ if (!res.ok && res.needsVerification) {
54
+ log(`Liberar ${connectorId}:${actionKey} (${STATE_LABEL[state]}) deixa o agente fazer mais sozinho.`);
55
+ const interactive = opts.interactive ?? Boolean(process.stdin.isTTY);
56
+ if (!interactive) {
57
+ console.error("Sem terminal para acompanhar a confirmação no navegador. Nada mudou.");
58
+ return 1;
59
+ }
60
+ const code = await verify(ctx, "perm_set", [{ file: `${connectorId}:${actionKey}`, key: state }], opts);
61
+ if (!code)
62
+ return 1;
63
+ res = await callApi(ctx.apiUrl, ctx.token, "/api/cli/permissions/set", { spaceId: ctx.spaceId, changes, verification: code });
64
+ }
65
+ if (!res.ok) {
66
+ refused(res, "A permissão não mudou");
67
+ return 1;
68
+ }
69
+ const applied = res.applied;
70
+ if (applied.length === 0)
71
+ log(`${connectorId}:${actionKey} já estava como ${STATE_LABEL[state]}. Nada mudou.`);
72
+ else
73
+ log(`✓ ${connectorId}:${actionKey}: ${STATE_LABEL[applied[0].from]} → ${STATE_LABEL[applied[0].to]}.`);
74
+ return 0;
75
+ }
76
+ export async function runPermissoes(args, opts = {}) {
77
+ const [first, ...rest] = args;
78
+ if (first === "set") {
79
+ const [connectorId, actionKey, stateArg, folder] = rest;
80
+ const state = parseStateArg(stateArg);
81
+ if (!connectorId || !actionKey || !state) {
82
+ console.error(USAGE);
83
+ return 1;
84
+ }
85
+ return set(resolve(folder ?? process.cwd()), connectorId, actionKey, state, opts);
86
+ }
87
+ if (first === "--help" || first === "-h" || first === "help") {
88
+ console.log(USAGE);
89
+ return 0;
90
+ }
91
+ return list(resolve(first && first !== "list" ? first : (rest[0] ?? process.cwd())), opts);
92
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnispace",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Omni CLI (alpha): transforma uma pasta da sua máquina em um espaço Omni e sincroniza entre máquinas.",
5
5
  "type": "module",
6
6
  "bin": {